Spring IoC 循環依賴的處理
- 2020 年 6 月 27 日
- 筆記
- Spring IoC
前言
本系列全部基於 Spring 5.2.2.BUILD-SNAPSHOT
版本。因為 Spring 整個體系太過於龐大,所以只會進行關鍵部分的源碼解析。
本篇文章主要介紹 Spring IoC 是怎麼解決循環依賴的問題的。
正文
什麼是循環依賴
循環依賴就是循環引用,就是兩個或多個 bean
相互之間的持有對方,比如A引用B,B引用A,像下面偽程式碼所示:
public class A {
private B b;
// 省略get和set方法...
}
public class B {
private A a;
// 省略get和set方法...
}
Spring 如何解決循環依賴
Spring IoC 容器對循環依賴的處理有三種情況:
- 構造器循環依賴:此依賴 Spring 無法處理,直接拋出
BeanCurrentlylnCreationException
異常。 - 單例作用域下的
setter
循環依賴:此依賴 Spring 通過三級快取來解決。 - 非單例的循環依賴:此依賴 Spring 無法處理,直接拋出
BeanCurrentlylnCreationException
異常。
構造器循環依賴
還是假設上面的A和B類是構造器循環依賴,如下所示:
public class A {
private B b;
public A(B b) {
this.b = b;
}
// 省略get和set方法...
}
public class B {
private A a;
public B(A a) {
this.a = a;
}
// 省略get和set方法...
}
然後我們在 XML 中配置了構造器自動注入,如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="a" class="com.leisurexi.ioc.circular.reference.A" autowire="constructor" />
<bean id="b" class="com.leisurexi.ioc.circular.reference.B" autowire="constructor" />
</beans>
那麼我們在獲取 A 時,首先會進入 doGetBean()
方法(該方法在Spring IoC bean 的載入中分析過),會進行到如下程式碼塊:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 省略其它程式碼...
// 如果 bean 的作用域是單例
if (mbd.isSingleton()) {
// 創建和註冊單例 bean
sharedInstance = getSingleton(beanName, () -> {
try {
// 創建 bean 實例
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
// 獲取bean實例
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 省略其它程式碼...
}
上面方法中的 getSingleton()
方法會判斷是否是第一次創建該 bean
,如果是第一次會先去創建 bean
,也就是調用 ObjectFacoty
的 getObject()
方法,即調用 createBean()
方法創建 bean
前,會先將當前正要創建的 bean
記錄在快取 singletonsCurrentlyInCreation
中。
在創建A時發現依賴 B,便先去創建 B;B在創建時發現依賴A,此時A因為是通過構造函數創建,所以沒創建完,便又去創建A,發現A存在於 singletonsCurrentlyInCreation
,即正在創建中,便拋出 BeanCurrentlylnCreationException
異常。
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
// 加鎖
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
// 一級快取中不存在當前 bean,也就是當前 bean 第一次創建
if (singletonObject == null) {
// 如果當前正在銷毀 singletons,拋出異常
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
// 創建單例 bean 之前的回調
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// 獲取 bean 實例
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
// 省略異常處理...
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
// 創建單例 bean 之後的回調
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 將 singletonObject 放入一級快取,並從二級和三級快取中移除
addSingleton(beanName, singletonObject);
}
}
// 返回 bean 實例
return singletonObject;
}
}
// 單例 bean 創建前的回調方法,默認實現是將 beanName 加入到當前正在創建 bean 的快取中,
// 這樣便可以對循環依賴進行檢測
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
// 單例 bean 創建後的回調方法,默認實現是將 beanName 從當前正在創建 bean 的快取中移除
protected void afterSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 這邊bean已經初始化完成了,放入一級快取
this.singletonObjects.put(beanName, singletonObject);
// 移除三級快取
this.singletonFactories.remove(beanName);
// 移除二級快取
this.earlySingletonObjects.remove(beanName);
// 將 beanName 添加到已註冊 bean 快取中
this.registeredSingletons.add(beanName);
}
}
setter循環依賴
還是假設上面的A和B類是 field 屬性依賴注入循環依賴,如下所示:
public class A {
private B b;
// 省略get和set方法...
}
public class B {
private A a;
// 省略get和set方法...
}
然後我們在 XML 中配置了按照類型自動注入,如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="//www.springframework.org/schema/beans"
xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="a" class="com.leisurexi.ioc.circular.reference.A" autowire="byType" />
<bean id="b" class="com.leisurexi.ioc.circular.reference.B" autowire="byType" />
</beans>
Spring 在解決單例循環依賴時引入了三級快取,如下所示:
// 一級快取,存儲已經初始化完成的bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二級快取,存儲已經實例化完成的bean
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三級快取,存儲創建bean實例的ObjectFactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 按先後順序記錄已經註冊的單例bean
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
首先在創建A時,會進入到 doCreateBean()
方法(前面的流程可以查看Spring IoC bean 的創建一文),如下:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
// 獲取bean的實例
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
// 通過構造函數反射創建bean的實例,但是屬性並未賦值
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 獲取bean的實例
final Object bean = instanceWrapper.getWrappedInstance();
// 省略其它程式碼...
// bean的作用域是單例 && 允許循環引用 && 當前bean正在創建中
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
// 如果允許bean提前曝光
if (earlySingletonExposure) {
// 將beanName和ObjectFactory形成的key-value對放入singletonFactories快取中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 省略其它程式碼...
}
在調用 addSingletonFactory()
方法前A的實例已經創建出來了,只是還未進行屬性賦值和初始化階段,接下來將它放入了三級快取中,如下:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
// 加鎖
synchronized (this.singletonObjects) {
// 如果一級快取中不包含當前bean
if (!this.singletonObjects.containsKey(beanName)) {
// 將ObjectFactory放入三級快取
this.singletonFactories.put(beanName, singletonFactory);
// 從二級快取中移除
this.earlySingletonObjects.remove(beanName);
// 將beanName加入到已經註冊過的單例bean快取中
this.registeredSingletons.add(beanName);
}
}
}
接下來A進行屬性賦值階段(會在後續文章中單獨分析這個階段),發現依賴B,便去獲取B,發現B還沒有被創建,所以走創建流程;在B進入屬性賦值階段時發現依賴A,就去調用 getBean()
方法獲取A,此時會進入 getSingleton()
方法(該方法的調用流程在Spring IoC bean 的載入一文中分析過),如下:
public Object getSingleton(String beanName) {
// allowEarlyReference設置為true表示允許早期依賴
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 先從一級快取中,檢查單例快取是否存在
Object singletonObject = this.singletonObjects.get(beanName);
// 如果為空,並且當前bean正在創建中,鎖定全局變數進行處理
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 從二級快取中獲取
singletonObject = this.earlySingletonObjects.get(beanName);
// 二級快取為空 && bean允許提前曝光
if (singletonObject == null && allowEarlyReference) {
// 從三級快取中獲取bean對應的ObjectFactory
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 調用預先設定的getObject(),獲取bean實例
singletonObject = singletonFactory.getObject();
// 放入到二級快取中,並從三級快取中刪除
// 這時bean已經實例化完但還未初始化完
// 在該bean未初始化完時如果有別的bean引用該bean,可以直接從二級快取中取出返回
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
嘗試一級快取 singletonObjects
(肯定沒有,因為A還沒初始化完全),嘗試二級快取 earlySingletonObjects
(也沒有),嘗試三級快取 singletonFactories
,由於A通過 ObjectFactory
將自己提前曝光了,所以B能夠通過 ObjectFactory.getObject()
拿到A對象(雖然A還沒有初始化完全,但是總比沒有好呀)。B拿到A後順利創建並初始化完成,調用上面分析過的 addSingleton()
方法將自己放入一級快取中。此時返回A中,A也能順利拿到完全初始化的B進行後續的階段,最後也將自己放入一級快取中,並從二級和三級快取中移除。
過程圖如下所示:
非單例循環依賴
對於非單例的 bean
,Spring 容器無法完成依賴注入,因為 Spring 容器不進行快取,因此無法提前暴露一個創建中的 bean
。
總結
本文主要介紹了 Spring 對三種循環依賴的處理,其實還有一種欄位循環依賴,比如 @Autowired
註解標註的欄位,但它和 setter
循環依賴的解決方法一樣,這裡就沒有多說。
最後,我模仿 Spring 寫了一個精簡版,程式碼會持續更新。地址://github.com/leisurexi/tiny-spring。
參考
- 《Spring 源碼深度解析》—— 郝佳
- //juejin.im/post/5c98a7b4f265da60ee12e9b2