1. 程式人生 > >Spring IoC 迴圈依賴的處理

Spring IoC 迴圈依賴的處理

# 前言 本系列全部基於 `Spring 5.2.2.BUILD-SNAPSHOT` 版本。因為 Spring 整個體系太過於龐大,所以只會進行關鍵部分的原始碼解析。 本篇文章主要介紹 Spring IoC 是怎麼解決迴圈依賴的問題的。 # 正文 ## 什麼是迴圈依賴 迴圈依賴就是迴圈引用,就是兩個或多個 `bean` 相互之間的持有對方,比如A引用B,B引用A,像下面虛擬碼所示: ```java public class A { private B b; // 省略get和set方法... } ``` ```java public class B { private A a; // 省略get和set方法... } ``` ## Spring 如何解決迴圈依賴 Spring IoC 容器對迴圈依賴的處理有三種情況: 1. 構造器迴圈依賴:此依賴 Spring 無法處理,直接丟擲 `BeanCurrentlylnCreationException` 異常。 2. 單例作用域下的 `setter` 迴圈依賴:此依賴 Spring 通過三級快取來解決。 3. 非單例的迴圈依賴:此依賴 Spring 無法處理,直接丟擲 `BeanCurrentlylnCreationException` 異常。 ### 構造器迴圈依賴 還是假設上面的A和B類是構造器迴圈依賴,如下所示: ```java public class A { private B b; public A(B b) { this.b = b; } // 省略get和set方法... } ``` ```java public class B { private A a; public B(A a) { this.a = a; } // 省略get和set方法... } ``` 然後我們在 XML 中配置了構造器自動注入,如下: ```xml ``` 那麼我們在獲取 A 時,首先會進入 `doGetBean()` 方法(該方法在[Spring IoC bean 的載入](https://www.cnblogs.com/leisurexi/p/13194515.html)中分析過),會進行到如下程式碼塊: ```java protected T doGetBean(final String name, @Nullable final Class 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` 異常。 ```java 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 屬性依賴注入迴圈依賴,如下所示: ```java public class A { private B b; // 省略get和set方法... } ``` ```java public class B { private A a; // 省略get和set方法... } ``` 然後我們在 XML 中配置了按照型別自動注入,如下: ```xml
``` Spring 在解決單例迴圈依賴時引入了三級快取,如下所示: ```java // 一級快取,儲存已經初始化完成的bean private final Map singletonObjects = new ConcurrentHashMap<>(256); // 二級快取,儲存已經例項化完成的bean private final Map earlySingletonObjects = new HashMap<>(16); // 三級快取,儲存建立bean例項的ObjectFactory private final Map> singletonFactories = new HashMap<>(16); // 按先後順序記錄已經註冊的單例bean private final Set registeredSingletons = new LinkedHashSet<>(256); ``` 首先在建立A時,會進入到 `doCreateBean()` 方法(前面的流程可以檢視[Spring IoC bean 的建立](https://www.cnblogs.com/leisurexi/p/13196998.html)一文),如下: ```java 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的例項已經創建出來了,只是還未進行屬性賦值和初始化階段,接下來將它放入了三級快取中,如下: ```java 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 的載入](https://www.cnblogs.com/leisurexi/p/13194515.html)一文中分析過),如下: ```java 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進行後續的階段,最後也將自己放入一級快取中,並從二級和三級快取中移除。 過程圖如下所示: ![](http://ww1.sinaimg.cn/large/006Vpl27ly1geq1d1g2x5j30qz0jddgu.jpg) ### 非單例迴圈依賴 對於非單例的 `bean`,Spring 容器無法完成依賴注入,因為 Spring 容器不進行快取,因此無法提前暴露一個建立中的 `bean`。 # 總結 本文主要介紹了 Spring 對三種迴圈依賴的處理,其實還有一種欄位迴圈依賴,比如 `@Autowired` 註解標註的欄位,但它和 `setter` 迴圈依賴的解決方法一樣,這裡就沒有多說。 > 最後,我模仿 Spring 寫了一個精簡版,程式碼會持續更新。地址:[https://github.com/leisurexi/tiny-spring](https://github.com/leisurexi/tiny-spring)。 # 參考 * 《Spring 原始碼深度解析》—— 郝佳 * https://juejin.im/post/5c98a7b4f265da60