1. 程式人生 > >死磕Spring之IoC篇 - 單例 Bean 的迴圈依賴處理

死磕Spring之IoC篇 - 單例 Bean 的迴圈依賴處理

> 該系列文章是本人在學習 Spring 的過程中總結下來的,裡面涉及到相關原始碼,可能對讀者不太友好,請結合我的原始碼註釋 [Spring 原始碼分析 GitHub 地址](https://github.com/liu844869663/spring-framework) 進行閱讀 > > Spring 版本:5.1.14.RELEASE > > 開始閱讀這一系列文章之前,建議先檢視[**《深入瞭解 Spring IoC(面試題)》**](https://www.cnblogs.com/lifullmoon/p/14422101.html)這一篇文章 > > 該系列其他文章請檢視:[**《死磕 Spring 之 IoC 篇 - 文章導讀》**](https://www.cnblogs.com/lifullmoon/p/14436372.html) ## 單例 Bean 的迴圈依賴處理 我們先回到[**《Bean 的建立過程》**](https://www.cnblogs.com/lifullmoon/p/14452842.html)中的**“從快取中獲取單例 Bean”**小節,當載入一個 Bean 時,會嘗試從**快取**(三個 Map)中獲取物件,如果未命中則進入後面建立 Bean 的過程。再來看到[**《Bean 的建立過程》**](https://www.cnblogs.com/lifullmoon/p/14452842.html)中的**“提前暴露當前 Bean”**小節,當獲取到一個例項物件(還未設定屬性和初始化)後,會將這個“早期物件”放入前面的快取中(第三個 Map),這裡暴露的物件實際是一個 ObjectFactory,可以通過它獲取“早期物件”。這樣一來,在後面設定屬性的過程中,如果需要依賴注入其他 Bean,且存在迴圈依賴,那麼上面的**快取**就避免了這個問題。接下來,將會分析 Spring 處理迴圈依賴的相關過程。 ### 這裡的迴圈依賴是什麼? 迴圈依賴,其實就是迴圈引用,就是兩個或者兩個以上的 Bean 互相引用對方,最終形成一個閉環,如 A 依賴 B,B 依賴 C,C 依賴 A。 例如定義下面兩個物件: 學生類 ```java public class Student { private Long id; private String name; @Autowired private ClassRoom classRoom; // 省略 getter、setter } ``` 教室類 ```java public class ClassRoom { private String name; @Autowired private Collection students; // 省略 getter、setter } ``` 當載入 Student 這個物件時,需要注入一個 ClassRoom 物件,就需要去載入 ClassRoom 這個物件,此時又要去依賴注入所有的 Student 物件,這裡的 Student 和 ClassRoom 就存在迴圈依賴,那麼一直這樣迴圈下去,除非有**終結條件**。 Spring 只處理**單例** Bean 的迴圈依賴,**原型**模式的 Bean 如果存在迴圈依賴直接**丟擲異常**,**單例** Bean 的迴圈依賴的**場景**有兩種: - 構造器注入出現迴圈依賴 - 欄位(或 Setter)注入出現迴圈依賴 對於構造器注入出現快取依賴,Spring 是無法解決的,因為當前 Bean 還未例項化,無法提前暴露物件,所以只能丟擲異常,**接下來我們分析的都是欄位(或 Setter)注入出現迴圈依賴的處理** ### 迴圈依賴的處理 #### 1. 嘗試從快取中獲取單例 Bean 可以先回到[**《Bean 的建立過程》**](https://www.cnblogs.com/lifullmoon/p/14452842.html)中的**“從快取中獲取單例 Bean”**小節,在獲取一個 Bean 過程中,首先會從快取中嘗試獲取物件,對應程式碼段: ```java // 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 Bean - `earlySingletonObjects`(二級 Map),裡面會儲存從 **三級 Map** 獲取到的正在初始化的 Bean - `singletonFactories`(三級 Map),裡面儲存了正在初始化的 Bean 對應的 ObjectFactory 實現類,呼叫其 getObject() 方法返回正在初始化的 Bean 物件(僅例項化還沒完全初始化好) 過程如下: 1. **【一級 Map】**從單例快取 `singletonObjects` 中獲取 beanName 對應的 Bean 2. 如果**一級 Map**中不存在,且當前 beanName 正在建立 1. 對 `singletonObjects` 加鎖 2. **【二級 Map】**從 `earlySingletonObjects` 集合中獲取,裡面會儲存從 **三級 Map** 獲取到的正在初始化的 Bean 3. 如果**二級 Map** 中不存在,且允許提前建立 1. **【三級 Map】**從 `singletonFactories` 中獲取對應的 ObjectFactory 實現類,如果從**三級 Map** 中存在對應的物件,則進行下面的處理 2. 呼叫 ObjectFactory#getOject() 方法,獲取目標 Bean 物件(早期半成品) 3. 將目標物件放入**二級 Map** 4. 從**三級 Map**移除 beanName 3. 返回從快取中獲取的物件 #### 2. 提前暴露當前 Bean 回到[**《Bean 的建立過程》**](https://www.cnblogs.com/lifullmoon/p/14452842.html)中的**“提前暴露當前 Bean”**小節,在獲取到例項物件後,如果是單例模式,則提前暴露這個例項物件,對應程式碼段: ```java // 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 實現類,如下: ```java // 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 實現類後,就需要往**快取**中存放了,如下: ```java // 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 後,會快取起來,如下: ```java // 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