本文,咱們來看一下 Spring 是如何解決循環依賴問題的。在本篇文章中,我會首先向你們介紹一下什麼是循環依賴。而後,進入源碼分析階段。爲了更好的說明 Spring 解決循環依賴的辦法,我將會從獲取 bean 的方法getBean(String)
開始,把整個調用過程梳理一遍。梳理完後,再來詳細分析源碼。經過這幾步的講解,但願讓你們可以弄懂什麼是循環依賴,以及如何解循環依賴。java
循環依賴相關的源碼自己不是很複雜,不過這裏要先介紹大量的前置知識。否則這些源碼看起來很簡單,但讀起來可能卻也不知所云。那下面咱們先來了解一下什麼是循環依賴。緩存
所謂的循環依賴是指,A 依賴 B,B 又依賴 A,它們之間造成了循環依賴。或者是 A 依賴 B,B 依賴 C,C 又依賴 A。它們之間的依賴關係以下:app
這裏以兩個類直接相互依賴爲例,他們的實現代碼可能以下:ide
public class BeanB { private BeanA beanA; // 省略 getter/setter } public class BeanA { private BeanB beanB; }
配置信息以下:源碼分析
<bean id="beanA" class="xyz.coolblog.BeanA"> <property name="beanB" ref="beanB"/> </bean> <bean id="beanB" class="xyz.coolblog.BeanB"> <property name="beanA" ref="beanA"/> </bean>
IOC 容器在讀到上面的配置時,會按照順序,先去實例化 beanA。而後發現 beanA 依賴於 beanB,接在又去實例化 beanB。實例化 beanB 時,發現 beanB 又依賴於 beanA。若是容器不處理循環依賴的話,容器會無限執行上面的流程,直到內存溢出,程序崩潰。固然,Spring 是不會讓這種狀況發生的。在容器再次發現 beanB 依賴於 beanA 時,容器會獲取 beanA 對象的一個早期的引用(early reference),並把這個早期引用注入到 beanB 中,讓 beanB 先完成實例化。beanB 完成實例化,beanA 就能夠獲取到 beanB 的引用,beanA 隨之完成實例化。這裏你們可能不知道「早期引用」是什麼意思,這裏先彆着急,我會在下一章進行說明。ui
好了,本章先到這裏,咱們繼續往下看。this
在進行源碼分析前,咱們先來看一組緩存的定義。以下:spa
/** Cache of singleton objects: bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256); /** Cache of singleton factories: bean name --> ObjectFactory */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16); /** Cache of early singleton objects: bean name --> bean instance */ private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
根據緩存變量上面的註釋,你們應該能大體瞭解他們的用途。我這裏簡單說明一下吧:3d
緩存 | 用途 |
---|---|
singletonObjects | 用於存放徹底初始化好的 bean,從該緩存中取出的 bean 能夠直接使用 |
earlySingletonObjects | 存放原始的 bean 對象(還沒有填充屬性),用於解決循環依賴 |
singletonFactories | 存放 bean 工廠對象,用於解決循環依賴 |
上一章提到了」早期引用「,所謂的」早期引用「是指向原始對象的引用。所謂的原始對象是指剛建立好的對象,但還未填充屬性。這樣講你們不知道你們聽明白了沒,不過沒聽明白也沒關係。簡單作個實驗就知道了,這裏咱們先定義一個對象 Room:代理
/** Room 包含了一些電器 */ public class Room { private String television; private String airConditioner; private String refrigerator; private String washer; // 省略 getter/setter }
配置以下:
<bean id="room" class="xyz.coolblog.demo.Room"> <property name="television" value="Xiaomi"/> <property name="airConditioner" value="Gree"/> <property name="refrigerator" value="Haier"/> <property name="washer" value="Siemens"/> </bean>
咱們先看一下徹底實例化好後的 bean 長什麼樣的。以下:
從調試信息中能夠看得出,Room 的每一個成員變量都被賦上值了。而後咱們再來看一下「原始的 bean 對象」長的是什麼樣的,以下:
結果比較明顯了,全部字段都是 null。這裏的 bean 和上面的 bean 指向的是同一個對象Room@1567
,但如今這個對象全部字段都是 null,咱們把這種對象成爲原始的對象。形象點說,上面的 bean 對象是一個裝修好的房子,能夠拎包入住了。而這裏的 bean 對象仍是個毛坯房,還要裝修一下(填充屬性)才行。
本節,咱們來了解從 Spring IOC 容器中獲取 bean 實例的流程(簡化版),這對咱們後續的源碼分析會有比較大的幫助。先看圖:
先來簡單介紹一下這張圖,這張圖是一個簡化後的流程圖。開始流程圖中只有一條執行路徑,在條件 sharedInstance != null 這裏出現了岔路,造成了綠色和紅色兩條路徑。在上圖中,讀取/添加緩存的方法我用藍色的框和☆標註了出來。至於虛線的箭頭,和虛線框裏的路徑,這個下面會說到。
我來按照上面的圖,分析一下整個流程的執行順序。這個流程從 getBean 方法開始,getBean 是個空殼方法,全部邏輯都在 doGetBean 方法中。doGetBean 首先會調用 getSingleton(beanName) 方法獲取 sharedInstance,sharedInstance 多是徹底實例化好的 bean,也多是一個原始的 bean,固然也有多是 null。若是不爲 null,則走綠色的那條路徑。再經 getObjectForBeanInstance 這一步處理後,綠色的這條執行路徑就結束了。
咱們再來看一下紅色的那條執行路徑,也就是 sharedInstance = null 的狀況。在第一次獲取某個 bean 的時候,緩存中是沒有記錄的,因此這個時候要走建立邏輯。上圖中的 getSingleton(beanName,
new ObjectFactory<Object>() {...}) 方法會建立一個 bean 實例,上圖虛線路徑指的是 getSingleton 方法內部調用的兩個方法,其邏輯以下:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { // 省略部分代碼 singletonObject = singletonFactory.getObject(); // ... addSingleton(beanName, singletonObject); }
如上所示,getSingleton 會在內部先調用 getObject 方法建立 singletonObject,而後再調用 addSingleton 將 singletonObject 放入緩存中。getObject 在內部代用了 createBean 方法,createBean 方法基本上也屬於空殼方法,更多的邏輯是寫在 doCreateBean 方法中的。doCreateBean 方法中的邏輯不少,其首先調用了 createBeanInstance 方法建立了一個原始的 bean 對象,隨後調用 addSingletonFactory 方法向緩存中添加單例 bean 工廠,從該工廠能夠獲取原始對象的引用,也就是所謂的「早期引用」。再以後,繼續調用 populateBean 方法向原始 bean 對象中填充屬性,並解析依賴。getObject 執行完成後,會返回徹底實例化好的 bean。緊接着再調用 addSingleton 把徹底實例化好的 bean 對象放入緩存中。到這裏,紅色執行路徑差很少也就要結束的。
我這裏沒有把 getObject、addSingleton 方法和 getSingleton(String, ObjectFactory) 並列畫在紅色的路徑裏,目的是想簡化一下方法的調用棧(都畫進來有點複雜)。咱們能夠進一步簡化上面的調用流程,好比下面:
這個流程看起來是否是簡單多了,命中緩存走綠色路徑,未命中走紅色的建立路徑。好了,本節先到這。
好了,通過前面的鋪墊,如今咱們終於能夠深刻源碼一探究竟了,想必你們已等不及了。那我不賣關子了,下面咱們按照方法的調用順序,依次來看一下循環依賴相關的代碼。以下:
protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { // ...... // 從緩存中獲取 bean 實例 Object sharedInstance = getSingleton(beanName); // ...... } public Object getSingleton(String beanName) { return getSingleton(beanName, true); } protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 從 singletonObjects 獲取實例,singletonObjects 中的實例都是準備好的 bean 實例,能夠直接使用 Object singletonObject = this.singletonObjects.get(beanName); // 判斷 beanName 對應的 bean 是否正在建立中 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 從 earlySingletonObjects 中獲取提早曝光的 bean singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 獲取相應的 bean 工廠 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 提早曝光 bean 實例(raw bean),用於解決循環依賴 singletonObject = singletonFactory.getObject(); // 將 singletonObject 放入緩存中,並將 singletonFactory 從緩存中移除 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }
上面的源碼中,doGetBean 所調用的方法 getSingleton(String) 是一個空殼方法,其主要邏輯在 getSingleton(String, boolean) 中。該方法邏輯比較簡單,首先從 singletonObjects 緩存中獲取 bean 實例。若未命中,再去 earlySingletonObjects 緩存中獲取原始 bean 實例。若是仍未命中,則從 singletonFactory 緩存中獲取 ObjectFactory 對象,而後再調用 getObject 方法獲取原始 bean 實例的應用,也就是早期引用。獲取成功後,將該實例放入 earlySingletonObjects 緩存中,並將 ObjectFactory 對象從 singletonFactories 移除。看完這個方法,咱們再來看看 getSingleton(String, ObjectFactory) 方法,這個方法也是在 doGetBean 中被調用的。此次我會把 doGetBean 的代碼多貼一點出來,以下:
protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { // ...... Object bean; // 從緩存中獲取 bean 實例 Object sharedInstance = getSingleton(beanName); // 這裏先忽略 args == null 這個條件 if (sharedInstance != null && args == null) { // 進行後續的處理 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // ...... // mbd.isSingleton() 用於判斷 bean 是不是單例模式 if (mbd.isSingleton()) { // 再次獲取 bean 實例 sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { try { // 建立 bean 實例,createBean 返回的 bean 是徹底實例化好的 return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } } }); // 進行後續的處理 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } // ...... } // ...... // 返回 bean return (T) bean; }
這裏的代碼邏輯和我在 2.3 回顧獲取 bean 的過程
一節的最後貼的主流程圖已經很接近了,對照那張圖和代碼中的註釋,你們應該能夠理解 doGetBean 方法了。繼續往下看:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { // ...... // 調用 getObject 方法建立 bean 實例 singletonObject = singletonFactory.getObject(); newSingleton = true; if (newSingleton) { // 添加 bean 到 singletonObjects 緩存中,並從其餘集合中將 bean 相關記錄移除 addSingleton(beanName, singletonObject); } // ...... // 返回 singletonObject return (singletonObject != NULL_OBJECT ? singletonObject : null); } } protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { // 將 <beanName, singletonObject> 映射存入 singletonObjects 中 this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT)); // 從其餘緩存中移除 beanName 相關映射 this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
上面的代碼中包含兩步操做,第一步操做是調用 getObject 建立 bean 實例,第二步是調用 addSingleton 方法將建立好的 bean 放入緩存中。代碼邏輯並不複雜,相信你們都能看懂。那麼接下來咱們繼續往下看,此次分析的是 doCreateBean 中的一些邏輯。以下:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; // ...... // ☆ 建立 bean 對象,並將 bean 對象包裹在 BeanWrapper 對象中返回 instanceWrapper = createBeanInstance(beanName, mbd, args); // 從 BeanWrapper 對象中獲取 bean 對象,這裏的 bean 指向的是一個原始的對象 final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null); /* * earlySingletonExposure 用於表示是否」提早暴露「原始對象的引用,用於解決循環依賴。 * 對於單例 bean,該變量通常爲 true。更詳細的解釋能夠參考我以前的文章 */ boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // ☆ 添加 bean 工廠對象到 singletonFactories 緩存中 addSingletonFactory(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { /* * 獲取原始對象的早期引用,在 getEarlyBeanReference 方法中,會執行 AOP * 相關邏輯。若 bean 未被 AOP 攔截,getEarlyBeanReference 原樣返回 * bean,因此你們能夠把 * return getEarlyBeanReference(beanName, mbd, bean) * 等價於: * return bean; */ return getEarlyBeanReference(beanName, mbd, bean); } }); } Object exposedObject = bean; // ...... // ☆ 填充屬性,解析依賴 populateBean(beanName, mbd, instanceWrapper); // ...... // 返回 bean 實例 return exposedObject; } protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { // 將 singletonFactory 添加到 singletonFactories 緩存中 this.singletonFactories.put(beanName, singletonFactory); // 從其餘緩存中移除相關記錄,即便沒有 this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
上面的代碼簡化了很多,不過看起來仍有點複雜。好在,上面代碼的主線邏輯比較簡單,由三個方法組成。以下:
1. 建立原始 bean 實例 → createBeanInstance(beanName, mbd, args) 2. 添加原始對象工廠對象到 singletonFactories 緩存中 → addSingletonFactory(beanName, new ObjectFactory<Object>{...}) 3. 填充屬性,解析依賴 → populateBean(beanName, mbd, instanceWrapper)
到這裏,本節涉及到的源碼就分析完了。但是看完源碼後,咱們彷佛仍然不知道這些源碼是如何解決循環依賴問題的。難道本篇文章就到這裏了嗎?答案是否。下面我來解答這個問題,這裏我仍是以 BeanA 和 BeanB 兩個類相互依賴爲例。在上面的方法調用中,有幾個關鍵的地方,下面一一列舉出來:
1. 建立原始 bean 對象
instanceWrapper = createBeanInstance(beanName, mbd, args); final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
假設 beanA 先被建立,建立後的原始對象爲 BeanA@1234
,上面代碼中的 bean 變量指向就是這個對象。
2. 暴露早期引用
addSingletonFactory(beanName, new ObjectFactory<Object>() { @Override public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, bean); } });
beanA 指向的原始對象建立好後,就開始把指向原始對象的引用經過 ObjectFactory 暴露出去。getEarlyBeanReference 方法的第三個參數 bean 指向的正是 createBeanInstance 方法建立出原始 bean 對象 BeanA@1234。
3. 解析依賴
populateBean(beanName, mbd, instanceWrapper);
populateBean 用於向 beanA 這個原始對象中填充屬性,當它檢測到 beanA 依賴於 beanB 時,會首先去實例化 beanB。beanB 在此方法處也會解析本身的依賴,當它檢測到 beanA 這個依賴,因而調用 BeanFactry.getBean("beanA") 這個方法,從容器中獲取 beanA。
4. 獲取早期引用
protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // ☆ 從緩存中獲取早期引用 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // ☆ 從 SingletonFactory 中獲取早期引用 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }
接着上面的步驟講,populateBean 調用 BeanFactry.getBean("beanA") 以獲取 beanB 的依賴。getBean("beanA") 會先調用 getSingleton("beanA"),嘗試從緩存中獲取 beanA。此時因爲 beanA 還沒徹底實例化好,因而 this.singletonObjects.get("beanA") 返回 null。接着 this.earlySingletonObjects.get("beanA") 也返回空,由於 beanA 早期引用還沒放入到這個緩存中。最後調用 singletonFactory.getObject() 返回 singletonObject,此時 singletonObject != null。singletonObject 指向 BeanA@1234,也就是 createBeanInstance 建立的原始對象。此時 beanB 獲取到了這個原始對象的引用,beanB 就能順利完成實例化。beanB 完成實例化後,beanA 就能獲取到 beanB 所指向的實例,beanA 隨之也完成了實例化工做。因爲 beanB.beanA 和 beanA 指向的是同一個對象 BeanA@1234,因此 beanB 中的 beanA 此時也處於可用狀態了。
以上的過程對應下面的流程圖:
到這裏,本篇文章差很少就快寫完了,不知道你們看懂了沒。這篇文章在前面作了大量的鋪墊,而後再進行源碼分析。相比於我以前寫的幾篇文章,本篇文章所對應的源碼難度上比以前簡單一些。但說實話也很差寫,我原本只想簡單介紹一下背景知識,而後直接進行源碼分析。可是又怕有的朋友看不懂,因此仍是用了大篇幅介紹的背景知識。這樣寫,可能有的朋友以爲比較囉嗦。可是考慮到你們的水平不一,爲了保證讓你們可以更好的理解,因此仍是儘可能寫的詳細一點。本篇文章總的來講寫的仍是有點累的,花了一些心思思考怎麼安排章節順序,怎麼簡化代碼和畫圖。若是你們看完這篇文章,以爲還不錯的話,不妨給個贊吧,也算是對個人鼓勵吧。
因爲我的的技術能力有限,若文章有錯誤不妥之處,歡迎你們指出來。好了,本篇文章到此結束,謝謝你們的閱讀。
更新時間 | 標題 |
---|---|
2018-05-30 | Spring IOC 容器源碼分析系列文章導讀 |
2018-06-01 | Spring IOC 容器源碼分析 - 獲取單例 bean |
2018-06-04 | Spring IOC 容器源碼分析 - 建立單例 bean 的過程 |
2018-06-06 | Spring IOC 容器源碼分析 - 建立原始 bean 對象 |
2018-06-08 | Spring IOC 容器源碼分析 - 循環依賴的解決辦法 |
2018-06-11 | Spring IOC 容器源碼分析 - 填充屬性到 bean 原始對象 |
2018-06-11 | Spring IOC 容器源碼分析 - 餘下的初始化工做 |
更新時間 | 標題 |
---|---|
2018-06-17 | Spring AOP 源碼分析系列文章導讀 |
2018-06-20 | Spring AOP 源碼分析 - 篩選合適的通知器 |
2018-06-20 | Spring AOP 源碼分析 - 建立代理對象 |
2018-06-22 | Spring AOP 源碼分析 - 攔截器鏈的執行過程 |
更新時間 | 標題 |
---|---|
2018-06-29 | Spring MVC 原理探祕 - 一個請求的旅行過程 |
2018-06-30 | Spring MVC 原理探祕 - 容器的建立過程 |
本文在知識共享許可協議 4.0 下發布,轉載需在明顯位置處註明出處
做者:coolblog.xyz
本文同步發佈在個人我的博客: http://www.coolblog.xyz
本做品採用知識共享署名-非商業性使用-禁止演繹 4.0 國際許可協議進行許可。