ioc框架 java_从零开始实现一个简易的Java MVC框架(三)--实现IOC
Spring中的IOC
IoC全稱是Inversion of Control,就是控制反轉,他其實不是spring獨有的特性或者說也不是java的特性,他是一種設計思想。而DI(Dependency Injection),即依賴注入就是Ioc的一種實現方式。關于Ioc和DI的具體定義和優缺點等大家可以自行查找資料了解一下,這里就不詳細贅述,總之spring的IoC功能很大程度上便捷了我們的開發工作。
在實現我們的Ioc之前,我們先了解一下spring的依賴注入,在spring中依賴注入有三種方式,分別是:
接口注入(Interface Injection)
設值方法注入(Setter Injection)
構造注入(Constructor Injection)
@Component
public class ComponentA {
@Autowired // 1.接口注入
private ComponentB componentB;
@Autowired // 2.設值方法注入
public void setComponentB(ComponentB componentB) {
this.componentB = componentB;
}
@Autowired // 3.構造注入
public ComponentA(ComponentB componentB) {
this.componentB = componentB;
}
}
循環依賴注入
如果只是實現依賴注入的話實際上很簡單,只要利用java的反射原理將對應的屬性‘注入’進去就可以了。但是必須要注意一個問題,那就是循環依賴問題。循環依賴就是類之間相互依賴形成了一個循環,比如A依賴于B,同時B又依賴于A,這就形成了相互循環。
// ComponentA
@Component
public class ComponentA {
@Autowired
private ComponentB componentB;
}
// ComponentB
@Component
public class ComponentB {
@Autowired
private ComponentA componentA;
}
那么在spring中又是如何解決循環依賴問題的呢,我們大致說一下原理。
如果要創建一個類,先把這個類放進'正在創建池'中,通過反射等創建實例,創建成功的話就把這個實例放入創建池中,并移除'正在創建池'中的這個類。每當實例中有依賴需要注入的話,就從創建池中找對應的實例注入進去,如果沒有找到實例,則先創建這個依賴。
利用了這個正在創建的中間狀態緩存,讓Bean的創建的時候即使有依賴還沒有實例化,可以先把Bean放進這個中間狀態,然后跑去創建那個依賴,假如那個依賴的類又依賴與這個Bean,那么只要在'正在創建池'中再把這個Bean拿出來,注入到這個依賴中,就可以保證Bean的依賴能夠實例化完成。再回頭來把這個依賴注入到Bean中,那么這個Bean也實例化完成了,就把這個Bean從'正在創建池'移到'創建完成池'中,就解決了循環依賴問題。
雖然spring巧妙的避免了循環依賴問題,但是事實上構造注入是無法避免循環依賴問題的。因為在實例化ComponentA的構造函數的時候必須得到ComponentB的實例,但是實例化ComponentB的構造函數的時候又必須有ComponentA的實例。這兩個Bean都不能通過反射實例化然后放到'正在創建池',所以無法解決循環依賴問題,這時候spring就會主動拋出BeanCurrentlyInCreationException異常避免死循環。
* 注意,前面講的這些都是基于spring的單例模式下的,如果是多例模式會有所不同,大家有興趣可以自行了解。
實現IOC
現在可以開始實現IOC功能了
增加注解
先在zbw.ioc包下創建一個annotation包,然后再創建一個Autowired的注解。這個注解的Target只有一個ElementType.FIELD,就是只能注解在屬性上。意味著我們目前只實現接口注入的功能。這樣可以避免構造注入造成的循環依賴問題無法解決,而且接口注入也是用的最多的方式了。如果想要實現設值方式注入大家可以自己去實現,實現原理幾乎都一樣。
package com.zbw.ioc.annotation;
import ...
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
}
實現IOC類
package com.zbw.ioc;
import ...
@Slf4j
public class Ioc {
/**
* Bean容器
*/
private BeanContainer beanContainer;
public Ioc() {
beanContainer = BeanContainer.getInstance();
}
/**
* 執行Ioc
*/
public void doIoc() {
for (Class> clz : beanContainer.getClasses()) { //遍歷Bean容器中所有的Bean
final Object targetBean = beanContainer.getBean(clz);
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) { //遍歷Bean中的所有屬性
if (field.isAnnotationPresent(Autowired.class)) {// 如果該屬性被Autowired注解,則對其注入
final Class> fieldClass = field.getType();
Object fieldValue = getClassInstance(fieldClass);
if (null != fieldValue) {
ClassUtil.setField(field, targetBean, fieldValue);
} else {
throw new RuntimeException("無法注入對應的類,目標類型:" + fieldClass.getName());
}
}
}
}
}
/**
* 根據Class獲取其實例或者實現類
*/
private Object getClassInstance(final Class> clz) {
return Optional
.ofNullable(beanContainer.getBean(clz))
.orElseGet(() -> {
Class> implementClass = getImplementClass(clz);
if (null != implementClass) {
return beanContainer.getBean(implementClass);
}
return null;
});
}
/**
* 獲取接口的實現類
*/
private Class> getImplementClass(final Class> interfaceClass) {
return beanContainer.getClassesBySuper(interfaceClass)
.stream()
.findFirst()
.orElse(null);
}
}
在知道IOC的原理之后發現其實真的是非常簡單,這里用了幾十行的代碼就實現了IOC的功能。
首先在Ioc類構造的時候先獲取到我們之前已經單例化的BeanContainer容器。
然后在doIoc()方法中就是正式實現IOC功能的了。
遍歷在BeanContainer容器的所有Bean
對每個Bean的Field屬性進行遍歷
如果某個Field屬性被Autowired注解,則調用getClassInstance()方法對其進行注入
getClassInstance()會根據Field的Class嘗試從Bean容器中獲取對應的實例,如果獲取到則返回該實例,如果獲取不到,則我們認定該Field為一個接口,我們就調用getImplementClass()方法來獲取這個接口的實現類Class,然后再根據這個實現類Class在Bean容器中獲取對應的實現類實例。
測試用例
為了測試我們的Ioc和之前寫的BeanContainer編寫正確,我們寫一下測試用例測試一下。
先在pom.xml添加junit的依賴
...
4.12
...
junit
junit
${junit.version}
test
然后在test包下添加DoodleController、DoodleService、DoodleServiceImpl三個類方便測試
// DoodleController
package com.zbw.bean;
@Controller
@Slf4j
public class DoodleController {
@Autowired
private DoodleService doodleService;
public void hello() {
log.info(doodleService.helloWord());
}
}
// DoodleService
package com.zbw.bean;
public interface DoodleService {
String helloWord();
}
// DoodleServiceImpl
package com.zbw.bean;
@Service
public class DoodleServiceImpl implements DoodleService{
@Override
public String helloWord() {
return "hello word";
}
}
再編寫IocTest的測試用例
package com.zbw.ioc;
import ...
@Slf4j
public class IocTest {
@Test
public void doIoc() {
BeanContainer beanContainer = BeanContainer.getInstance();
beanContainer.loadBeans("com.zbw");
new Ioc().doIoc();
DoodleController controller = (DoodleController) beanContainer.getBean(DoodleController.class);
controller.hello();
}
}
看到在DoodleController中輸出了DoodleServiceImpl的helloWord()方法里的字符串,說明DoodleController中的DoodleService已經成功注入了DoodleServiceImpl。那么我們的IOC的功能也完成了。
總結
以上是生活随笔為你收集整理的ioc框架 java_从零开始实现一个简易的Java MVC框架(三)--实现IOC的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: centos7下双网卡绑定(bondin
- 下一篇: java美元兑换,(Java实现) 美元