該系列文章是本人在學習 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 代理?
因為創建的代理對象需要關聯目標對象,在攔截處理的過程中需要根據目標對象執行被攔截的方法,所以這個目標對象最好是一個“成熟態”,而不是僅實例化還未初始化的一個對象。