該系列文章是本人在學習 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 代理,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/266214.html
標籤:Java
下一篇:學習筆記:Java泛型
