該系列文章是本人在學習 Spring 的過程中總結下來的,里面涉及到相關源碼,可能對讀者不太友好,請結合我的源碼注釋 Spring 源碼分析 GitHub 地址 進行閱讀
Spring 版本:5.1.14.RELEASE
開始閱讀這一系列文章之前,建議先查看《深入了解 Spring IoC(面試題)》這一篇文章
該系列其他文章請查看:《死磕 Spring 之 IoC 篇 - 文章導讀》
單例 Bean 的循環依賴處理
我們先回到《Bean 的創建過程》中的“從緩存中獲取單例 Bean”小節,當加載一個 Bean 時,會嘗試從緩存(三個 Map)中獲取對象,如果未命中則進入后面創建 Bean 的過程。再來看到《Bean 的創建過程》中的“提前暴露當前 Bean”小節,當獲取到一個實例對象(還未設置屬性和初始化)后,會將這個“早期對象”放入前面的緩存中(第三個 Map),這里暴露的對象實際是一個 ObjectFactory,可以通過它獲取“早期對象”。這樣一來,在后面設置屬性的過程中,如果需要依賴注入其他 Bean,且存在循環依賴,那么上面的緩存就避免了這個問題。接下來,將會分析 Spring 處理循環依賴的相關過程。
這里的循環依賴是什么?
循環依賴,其實就是循環引用,就是兩個或者兩個以上的 Bean 互相引用對方,最終形成一個閉環,如 A 依賴 B,B 依賴 C,C 依賴 A。
例如定義下面兩個對象:
學生類
public class Student {
private Long id;
private String name;
@Autowired
private ClassRoom classRoom;
// 省略 getter、setter
}
教室類
public class ClassRoom {
private String name;
@Autowired
private Collection<Student> students;
// 省略 getter、setter
}
當加載 Student 這個對象時,需要注入一個 ClassRoom 對象,就需要去加載 ClassRoom 這個對象,此時又要去依賴注入所有的 Student 對象,這里的 Student 和 ClassRoom 就存在循環依賴,那么一直這樣循環下去,除非有終結條件。
Spring 只處理單例 Bean 的循環依賴,原型模式的 Bean 如果存在循環依賴直接拋出異常,單例 Bean 的循環依賴的場景有兩種:
- 構造器注入出現循環依賴
- 字段(或 Setter)注入出現循環依賴
對於構造器注入出現緩存依賴,Spring 是無法解決的,因為當前 Bean 還未實例化,無法提前暴露對象,所以只能拋出異常,接下來我們分析的都是字段(或 Setter)注入出現循環依賴的處理
循環依賴的處理
1. 嘗試從緩存中獲取單例 Bean
可以先回到《Bean 的創建過程》中的“從緩存中獲取單例 Bean”小節,在獲取一個 Bean 過程中,首先會從緩存中嘗試獲取對象,對應代碼段:
// AbstractBeanFactory#doGetBean(...) 方法
Object sharedInstance = getSingleton(beanName);
// DefaultSingletonBeanRegistry.java
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
// DefaultSingletonBeanRegistry.java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// <1> **【一級 Map】**從單例緩存 `singletonObjects` 中獲取 beanName 對應的 Bean
Object singletonObject = this.singletonObjects.get(beanName);
// <2> 如果**一級 Map**中不存在,且當前 beanName 正在創建
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// <2.1> 對 `singletonObjects` 加鎖
synchronized (this.singletonObjects) {
// <2.2> **【二級 Map】**從 `earlySingletonObjects` 集合中獲取,里面會保存從 **三級 Map** 獲取到的正在初始化的 Bean
singletonObject = this.earlySingletonObjects.get(beanName);
// <2.3> 如果**二級 Map** 中不存在,且允許提前創建
if (singletonObject == null && allowEarlyReference) {
// <2.3.1> **【三級 Map】**從 `singletonFactories` 中獲取對應的 ObjectFactory 實現類
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 如果從**三級 Map** 中存在對應的對象,則進行下面的處理
if (singletonFactory != null) {
// <2.3.2> 調用 ObjectFactory#getOject() 方法,獲取目標 Bean 對象(早期半成品)
singletonObject = singletonFactory.getObject();
// <2.3.3> 將目標對象放入**二級 Map**
this.earlySingletonObjects.put(beanName, singletonObject);
// <2.3.4> 從**三級 Map**移除 `beanName`
this.singletonFactories.remove(beanName);
}
}
}
}
// <3> 返回從緩存中獲取的對象
return singletonObject;
}
這里的緩存指的就是上面三個 Map 對象:
singletonObjects(一級 Map):里面保存了所有已經初始化好的單例 Bean,也就是會保存 Spring IoC 容器中所有單例的 Spring BeanearlySingletonObjects(二級 Map),里面會保存從 三級 Map 獲取到的正在初始化的 BeansingletonFactories(三級 Map),里面保存了正在初始化的 Bean 對應的 ObjectFactory 實現類,調用其 getObject() 方法返回正在初始化的 Bean 對象(僅實例化還沒完全初始化好)
過程如下:
- 【一級 Map】從單例緩存
singletonObjects中獲取 beanName 對應的 Bean - 如果一級 Map中不存在,且當前 beanName 正在創建
- 對
singletonObjects加鎖 - 【二級 Map】從
earlySingletonObjects集合中獲取,里面會保存從 三級 Map 獲取到的正在初始化的 Bean - 如果二級 Map 中不存在,且允許提前創建
- 【三級 Map】從
singletonFactories中獲取對應的 ObjectFactory 實現類,如果從三級 Map 中存在對應的對象,則進行下面的處理 - 調用 ObjectFactory#getOject() 方法,獲取目標 Bean 對象(早期半成品)
- 將目標對象放入二級 Map
- 從三級 Map移除 beanName
- 【三級 Map】從
- 對
- 返回從緩存中獲取的對象
2. 提前暴露當前 Bean
回到《Bean 的創建過程》中的“提前暴露當前 Bean”小節,在獲取到實例對象后,如果是單例模式,則提前暴露這個實例對象,對應代碼段:
// AbstractAutowireCapableBeanFactory#doCreateBean(...) 方法
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
// <3> 提前暴露這個 `bean`,如果可以的話,目的是解決單例模式 Bean 的循環依賴注入
// <3.1> 判斷是否可以提前暴露
boolean earlySingletonExposure = (mbd.isSingleton() // 單例模式
&& this.allowCircularReferences // 允許循環依賴,默認為 true
&& isSingletonCurrentlyInCreation(beanName)); // 當前單例 bean 正在被創建,在前面已經標記過
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
/**
* <3.2>
* 創建一個 ObjectFactory 實現類,用於返回當前正在被創建的 `bean`,提前暴露,保存在 `singletonFactories` (**三級 Map**)緩存中
*
* 可以回到前面的 {@link AbstractBeanFactory#doGetBean#getSingleton(String)} 方法
* 加載 Bean 的過程會先從緩存中獲取單例 Bean,可以避免單例模式 Bean 循環依賴注入的問題
*/
addSingletonFactory(beanName,
// ObjectFactory 實現類
() -> getEarlyBeanReference(beanName, mbd, bean));
}
如果是單例模式、允許循環依賴(默認為 true)、當前單例 Bean 正在被創建(前面已經標記過),則提前暴露
這里會先通過 Lambda 表達式創建一個 ObjectFactory 實現類,如下:
// AbstractAutowireCapableBeanFactory.java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() // RootBeanDefinition 不是用戶定義的(由 Spring 解析出來的)
&& hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
入參 bean 為當前 Bean 的實例對象(未初始化),這個實現類允許通過 SmartInstantiationAwareBeanPostProcessor 對這個提前暴露的對象進行處理,最終會返回這個提前暴露的對象。注意,這里也可以返回一個代理對象。
有了這個 ObjectFactory 實現類后,就需要往緩存中存放了,如下:
// DefaultSingletonBeanRegistry.java
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
可以看到會將這個 ObjectFactory 往 singletonFactories (三級 Map)中存放,到這里對於 Spring 對單例 Bean 循環依賴的處理是不是就非常清晰了
3. 緩存單例 Bean
在完全初始化好一個單例 Bean 后,會緩存起來,如下:
// DefaultSingletonBeanRegistry.java
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
往 singletonObjects(一級 Map)存放當前單例 Bean,同時從 singletonFactories(三級 Map)和 earlySingletonObjects(二級 Map)中移除
總結
Spring 只處理單例 Bean 的字段(或 Setter)注入出現循環依賴,對於構造器注入出現的循環依賴會直接拋出異常。還有就是如果是通過 denpends-on 配置的依賴出現了循環,也會拋出異常,所以我覺得這里的“循環依賴”換做“循環依賴注入”是不是更合適一點
Spring 處理循環依賴的解決方案如下:
- Spring 在創建 Bean 的過程中,獲取到實例對象后會提前暴露出去,生成一個 ObjectFactory 對象,放入
singletonFactories(三級 Map)中 - 在后續設置屬性過程中,如果出現循環,則可以通過
singletonFactories(三級 Map)中對應的 ObjectFactory#getObject() 獲取這個早期對象,避免再次初始化
問題一:為什么需要上面的 二級 Map ?
因為通過 三級 Map獲取 Bean 會有相關 SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(..) 的處理,避免重復處理,處理后返回的可能是一個代理對象
例如在循環依賴中一個 Bean 可能被多個 Bean 依賴, A -> B(也依賴 A) -> C -> A,當你獲取 A 這個 Bean 時,后續 B 和 C 都要注入 A,沒有上面的 二級 Map的話,三級 Map 保存的 ObjectFactory 實現類會被調用兩次,會重復處理,可能出現問題,這樣做在性能上也有所提升
問題二:為什么不直接調用這個 ObjectFactory#getObject() 方法放入 二級Map 中,而需要上面的 三級 Map?
對於不涉及到 AOP 的 Bean 確實可以不需要
singletonFactories(三級 Map),但是 Spring AOP 就是 Spring 體系中的一員,如果沒有singletonFactories(三級 Map),意味着 Bean 在實例化后就要完成 AOP 代理,這樣違背了 Spring 的設計原則。Spring 是通過AnnotationAwareAspectJAutoProxyCreator這個后置處理器在完全創建好 Bean 后來完成 AOP 代理,而不是在實例化后就立馬進行 AOP 代理。如果出現了循環依賴,那沒有辦法,只有給 Bean 先創建代理對象,但是在沒有出現循環依賴的情況下,設計之初就是讓 Bean 在完全創建好后才完成 AOP 代理。
提示:
AnnotationAwareAspectJAutoProxyCreator是一個SmartInstantiationAwareBeanPostProcessor后置處理器,在它的 getEarlyBeanReference(..) 方法中可以創建代理對象。所以說對於上面的問題二,如果出現了循環依賴,如果是一個 AOP 代理對象,那只能給 Bean 先創建代理對象,設計之初就是讓 Bean 在完全創建好后才完成 AOP 代理。
為什么 Spring 的設計是讓 Bean 在完全創建好后才完成 AOP 代理?
因為創建的代理對象需要關聯目標對象,在攔截處理的過程中需要根據目標對象執行被攔截的方法,所以這個目標對象最好是一個“成熟態”,而不是僅實例化還未初始化的一個對象。
