IoC全稱是Inversion of Control,就是控制反轉,他其實不是spring獨有的特性或者說也不是java的特性,他是一種設計思想。而DI(Dependency Injection),即依賴注入就是Ioc的一種實現方式。關於Ioc和DI的具體定義和優缺點等你們能夠自行查找資料瞭解一下,這裏就不詳細贅述,總之spring的IoC功能很大程度上便捷了咱們的開發工做。java
在實現咱們的Ioc以前,咱們先了解一下spring的依賴注入,在spring中依賴注入有三種方式,分別是:git
@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,這就造成了相互循環。github
// ComponentA @Component public class ComponentA { @Autowired private ComponentB componentB; } // ComponentB @Component public class ComponentB { @Autowired private ComponentA componentA; }
那麼在spring中又是如何解決循環依賴問題的呢,咱們大體說一下原理。spring
若是要建立一個類,先把這個類放進'正在建立池'中,經過反射等建立實例,建立成功的話就把這個實例放入建立池中,並移除'正在建立池'中的這個類。每當實例中有依賴須要注入的話,就從建立池中找對應的實例注入進去,若是沒有找到實例,則先建立這個依賴。緩存
利用了這個正在建立的中間狀態緩存,讓Bean的建立的時候即便有依賴尚未實例化,能夠先把Bean放進這個中間狀態,而後跑去建立那個依賴,假如那個依賴的類又依賴與這個Bean,那麼只要在'正在建立池'中再把這個Bean拿出來,注入到這個依賴中,就能夠保證Bean的依賴可以實例化完成。再回頭來把這個依賴注入到Bean中,那麼這個Bean也實例化完成了,就把這個Bean從'正在建立池'移到'建立完成池'中,就解決了循環依賴問題。框架
雖然spring巧妙的避免了循環依賴問題,可是事實上構造注入是沒法避免循環依賴問題的。由於在實例化ComponentA
的構造函數的時候必須獲得ComponentB
的實例,可是實例化ComponentB
的構造函數的時候又必須有ComponentA
的實例。這兩個Bean都不能經過反射實例化而後放到'正在建立池',因此沒法解決循環依賴問題,這時候spring就會主動拋出BeanCurrentlyInCreationException
異常避免死循環。ide
* 注意,前面講的這些都是基於spring的單例模式下的,若是是多例模式會有所不一樣,你們有興趣能夠自行了解。函數
如今能夠開始實現IOC功能了測試
先在zbw.ioc包下建立一個annotation包,而後再建立一個Autowired
的註解。這個註解的Target只有一個ElementType.FIELD
,就是隻能註解在屬性上。意味着咱們目前只實現接口注入的功能。這樣能夠避免構造注入形成的循環依賴問題沒法解決,並且接口注入也是用的最多的方式了。若是想要實現設值方式注入你們能夠本身去實現,實現原理幾乎都同樣。優化
package com.zbw.ioc.annotation; import ... @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { }
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功能的了。
Autowired
註解,則調用getClassInstance()
方法對其進行注入getClassInstance()
會根據Field的Class嘗試從Bean容器中獲取對應的實例,若是獲取到則返回該實例,若是獲取不到,則咱們認定該Field爲一個接口,咱們就調用getImplementClass()
方法來獲取這個接口的實現類Class,而後再根據這個實現類Class在Bean容器中獲取對應的實現類實例。爲了測試咱們的Ioc和以前寫的BeanContainer編寫正確,咱們寫一下測試用例測試一下。
先在pom.xml添加junit的依賴
<properties> ... <junit.version>4.12</junit.version> </properties> <dependencies> ... <!-- junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> </dependencies>
而後在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的功能也完成了。
- 從零開始實現一個簡易的Java MVC框架(一)--前言
- 從零開始實現一個簡易的Java MVC框架(二)--實現Bean容器
- 從零開始實現一個簡易的Java MVC框架(三)--實現IOC
- 從零開始實現一個簡易的Java MVC框架(四)--實現AOP
- 從零開始實現一個簡易的Java MVC框架(五)--引入aspectj實現AOP切點
- 從零開始實現一個簡易的Java MVC框架(六)--增強AOP功能
- 從零開始實現一個簡易的Java MVC框架(七)--實現MVC
- 從零開始實現一個簡易的Java MVC框架(八)--製做Starter
- 從零開始實現一個簡易的Java MVC框架(九)--優化MVC代碼
源碼地址:doodle