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的依賴
<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