摘要:實(shí)例化時,發(fā)現(xiàn)又依賴于。一些緩存的介紹在進(jìn)行源碼分析前,我們先來看一組緩存的定義。可是看完源碼后,我們似乎仍然不知道這些源碼是如何解決循環(huán)依賴問題的。
1. 簡介
本文,我們來看一下 Spring 是如何解決循環(huán)依賴問題的。在本篇文章中,我會首先向大家介紹一下什么是循環(huán)依賴。然后,進(jìn)入源碼分析階段。為了更好的說明 Spring 解決循環(huán)依賴的辦法,我將會從獲取 bean 的方法getBean(String)開始,把整個調(diào)用過程梳理一遍。梳理完后,再來詳細(xì)分析源碼。通過這幾步的講解,希望讓大家能夠弄懂什么是循環(huán)依賴,以及如何解循環(huán)依賴。
循環(huán)依賴相關(guān)的源碼本身不是很復(fù)雜,不過這里要先介紹大量的前置知識。不然這些源碼看起來很簡單,但讀起來可能卻也不知所云。那下面我們先來了解一下什么是循環(huán)依賴。
2. 背景知識 2.1 什么是循環(huán)依賴所謂的循環(huán)依賴是指,A 依賴 B,B 又依賴 A,它們之間形成了循環(huán)依賴。或者是 A 依賴 B,B 依賴 C,C 又依賴 A。它們之間的依賴關(guān)系如下:
這里以兩個類直接相互依賴為例,他們的實(shí)現(xiàn)代碼可能如下:
public class BeanB { private BeanA beanA; // 省略 getter/setter } public class BeanA { private BeanB beanB; }
配置信息如下:
IOC 容器在讀到上面的配置時,會按照順序,先去實(shí)例化 beanA。然后發(fā)現(xiàn) beanA 依賴于 beanB,接在又去實(shí)例化 beanB。實(shí)例化 beanB 時,發(fā)現(xiàn) beanB 又依賴于 beanA。如果容器不處理循環(huán)依賴的話,容器會無限執(zhí)行上面的流程,直到內(nèi)存溢出,程序崩潰。當(dāng)然,Spring 是不會讓這種情況發(fā)生的。在容器再次發(fā)現(xiàn) beanB 依賴于 beanA 時,容器會獲取 beanA 對象的一個早期的引用(early reference),并把這個早期引用注入到 beanB 中,讓 beanB 先完成實(shí)例化。beanB 完成實(shí)例化,beanA 就可以獲取到 beanB 的引用,beanA 隨之完成實(shí)例化。這里大家可能不知道“早期引用”是什么意思,這里先別著急,我會在下一章進(jìn)行說明。
好了,本章先到這里,我們繼續(xù)往下看。
2.2 一些緩存的介紹在進(jìn)行源碼分析前,我們先來看一組緩存的定義。如下:
/** Cache of singleton objects: bean name --> bean instance */ private final MapsingletonObjects = new ConcurrentHashMap (256); /** Cache of singleton factories: bean name --> ObjectFactory */ private final Map > singletonFactories = new HashMap >(16); /** Cache of early singleton objects: bean name --> bean instance */ private final Map earlySingletonObjects = new HashMap (16);
根據(jù)緩存變量上面的注釋,大家應(yīng)該能大致了解他們的用途。我這里簡單說明一下吧:
緩存 | 用途 |
---|---|
singletonObjects | 用于存放完全初始化好的 bean,從該緩存中取出的 bean 可以直接使用 |
earlySingletonObjects | 存放原始的 bean 對象(尚未填充屬性),用于解決循環(huán)依賴 |
singletonFactories | 存放 bean 工廠對象,用于解決循環(huán)依賴 |
上一章提到了”早期引用“,所謂的”早期引用“是指向原始對象的引用。所謂的原始對象是指剛創(chuàng)建好的對象,但還未填充屬性。這樣講大家不知道大家聽明白了沒,不過沒聽明白也不要緊。簡單做個實(shí)驗就知道了,這里我們先定義一個對象 Room:
/** Room 包含了一些電器 */ public class Room { private String television; private String airConditioner; private String refrigerator; private String washer; // 省略 getter/setter }
配置如下:
我們先看一下完全實(shí)例化好后的 bean 長什么樣的。如下:
從調(diào)試信息中可以看得出,Room 的每個成員變量都被賦上值了。然后我們再來看一下“原始的 bean 對象”長的是什么樣的,如下:
結(jié)果比較明顯了,所有字段都是 null。這里的 bean 和上面的 bean 指向的是同一個對象Room@1567,但現(xiàn)在這個對象所有字段都是 null,我們把這種對象成為原始的對象。形象點(diǎn)說,上面的 bean 對象是一個裝修好的房子,可以拎包入住了。而這里的 bean 對象還是個毛坯房,還要裝修一下(填充屬性)才行。
2.3 回顧獲取 bean 的過程本節(jié),我們來了解從 Spring IOC 容器中獲取 bean 實(shí)例的流程(簡化版),這對我們后續(xù)的源碼分析會有比較大的幫助。先看圖:
先來簡單介紹一下這張圖,這張圖是一個簡化后的流程圖。開始流程圖中只有一條執(zhí)行路徑,在條件 sharedInstance != null 這里出現(xiàn)了岔路,形成了綠色和紅色兩條路徑。在上圖中,讀取/添加緩存的方法我用藍(lán)色的框和☆標(biāo)注了出來。至于虛線的箭頭,和虛線框里的路徑,這個下面會說到。
我來按照上面的圖,分析一下整個流程的執(zhí)行順序。這個流程從 getBean 方法開始,getBean 是個空殼方法,所有邏輯都在 doGetBean 方法中。doGetBean 首先會調(diào)用 getSingleton(beanName) 方法獲取 sharedInstance,sharedInstance 可能是完全實(shí)例化好的 bean,也可能是一個原始的 bean,當(dāng)然也有可能是 null。如果不為 null,則走綠色的那條路徑。再經(jīng) getObjectForBeanInstance 這一步處理后,綠色的這條執(zhí)行路徑就結(jié)束了。
我們再來看一下紅色的那條執(zhí)行路徑,也就是 sharedInstance = null 的情況。在第一次獲取某個 bean 的時候,緩存中是沒有記錄的,所以這個時候要走創(chuàng)建邏輯。上圖中的 getSingleton(beanName,
new ObjectFactory
public Object getSingleton(String beanName, ObjectFactory> singletonFactory) { // 省略部分代碼 singletonObject = singletonFactory.getObject(); // ... addSingleton(beanName, singletonObject); }
如上所示,getSingleton 會在內(nèi)部先調(diào)用 getObject 方法創(chuàng)建 singletonObject,然后再調(diào)用 addSingleton 將 singletonObject 放入緩存中。getObject 在內(nèi)部代用了 createBean 方法,createBean 方法基本上也屬于空殼方法,更多的邏輯是寫在 doCreateBean 方法中的。doCreateBean 方法中的邏輯很多,其首先調(diào)用了 createBeanInstance 方法創(chuàng)建了一個原始的 bean 對象,隨后調(diào)用 addSingletonFactory 方法向緩存中添加單例 bean 工廠,從該工廠可以獲取原始對象的引用,也就是所謂的“早期引用”。再之后,繼續(xù)調(diào)用 populateBean 方法向原始 bean 對象中填充屬性,并解析依賴。getObject 執(zhí)行完成后,會返回完全實(shí)例化好的 bean。緊接著再調(diào)用 addSingleton 把完全實(shí)例化好的 bean 對象放入緩存中。到這里,紅色執(zhí)行路徑差不多也就要結(jié)束的。
我這里沒有把 getObject、addSingleton 方法和 getSingleton(String, ObjectFactory) 并列畫在紅色的路徑里,目的是想簡化一下方法的調(diào)用棧(都畫進(jìn)來有點(diǎn)復(fù)雜)。我們可以進(jìn)一步簡化上面的調(diào)用流程,比如下面:
這個流程看起來是不是簡單多了,命中緩存走綠色路徑,未命中走紅色的創(chuàng)建路徑。好了,本節(jié)先到這。
3. 源碼分析好了,經(jīng)過前面的鋪墊,現(xiàn)在我們終于可以深入源碼一探究竟了,想必大家已等不及了。那我不賣關(guān)子了,下面我們按照方法的調(diào)用順序,依次來看一下循環(huán)依賴相關(guān)的代碼。如下:
protectedT doGetBean( final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { // ...... // 從緩存中獲取 bean 實(shí)例 Object sharedInstance = getSingleton(beanName); // ...... } public Object getSingleton(String beanName) { return getSingleton(beanName, true); } protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 從 singletonObjects 獲取實(shí)例,singletonObjects 中的實(shí)例都是準(zhǔn)備好的 bean 實(shí)例,可以直接使用 Object singletonObject = this.singletonObjects.get(beanName); // 判斷 beanName 對應(yīng)的 bean 是否正在創(chuàng)建中 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 從 earlySingletonObjects 中獲取提前曝光的 bean singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 獲取相應(yīng)的 bean 工廠 ObjectFactory> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 提前曝光 bean 實(shí)例(raw bean),用于解決循環(huán)依賴 singletonObject = singletonFactory.getObject(); // 將 singletonObject 放入緩存中,并將 singletonFactory 從緩存中移除 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return (singletonObject != NULL_OBJECT ? singletonObject : null); }
上面的源碼中,doGetBean 所調(diào)用的方法 getSingleton(String) 是一個空殼方法,其主要邏輯在 getSingleton(String, boolean) 中。該方法邏輯比較簡單,首先從 singletonObjects 緩存中獲取 bean 實(shí)例。若未命中,再去 earlySingletonObjects 緩存中獲取原始 bean 實(shí)例。如果仍未命中,則從 singletonFactory 緩存中獲取 ObjectFactory 對象,然后再調(diào)用 getObject 方法獲取原始 bean 實(shí)例的應(yīng)用,也就是早期引用。獲取成功后,將該實(shí)例放入 earlySingletonObjects 緩存中,并將 ObjectFactory 對象從 singletonFactories 移除。看完這個方法,我們再來看看 getSingleton(String, ObjectFactory) 方法,這個方法也是在 doGetBean 中被調(diào)用的。這次我會把 doGetBean 的代碼多貼一點(diǎn)出來,如下:
protectedT doGetBean( final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly) throws BeansException { // ...... Object bean; // 從緩存中獲取 bean 實(shí)例 Object sharedInstance = getSingleton(beanName); // 這里先忽略 args == null 這個條件 if (sharedInstance != null && args == null) { // 進(jìn)行后續(xù)的處理 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { // ...... // mbd.isSingleton() 用于判斷 bean 是否是單例模式 if (mbd.isSingleton()) { // 再次獲取 bean 實(shí)例 sharedInstance = getSingleton(beanName, new ObjectFactory
這里的代碼邏輯和我在 2.3 回顧獲取 bean 的過程 一節(jié)的最后貼的主流程圖已經(jīng)很接近了,對照那張圖和代碼中的注釋,大家應(yīng)該可以理解 doGetBean 方法了。繼續(xù)往下看:
public Object getSingleton(String beanName, ObjectFactory> singletonFactory) { synchronized (this.singletonObjects) { // ...... // 調(diào)用 getObject 方法創(chuàng)建 bean 實(shí)例 singletonObject = singletonFactory.getObject(); newSingleton = true; if (newSingleton) { // 添加 bean 到 singletonObjects 緩存中,并從其他集合中將 bean 相關(guān)記錄移除 addSingleton(beanName, singletonObject); } // ...... // 返回 singletonObject return (singletonObject != NULL_OBJECT ? singletonObject : null); } } protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { // 將映射存入 singletonObjects 中 this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT)); // 從其他緩存中移除 beanName 相關(guān)映射 this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
上面的代碼中包含兩步操作,第一步操作是調(diào)用 getObject 創(chuàng)建 bean 實(shí)例,第二步是調(diào)用 addSingleton 方法將創(chuàng)建好的 bean 放入緩存中。代碼邏輯并不復(fù)雜,相信大家都能看懂。那么接下來我們繼續(xù)往下看,這次分析的是 doCreateBean 中的一些邏輯。如下:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; // ...... // ☆ 創(chuàng)建 bean 對象,并將 bean 對象包裹在 BeanWrapper 對象中返回 instanceWrapper = createBeanInstance(beanName, mbd, args); // 從 BeanWrapper 對象中獲取 bean 對象,這里的 bean 指向的是一個原始的對象 final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null); /* * earlySingletonExposure 用于表示是否”提前暴露“原始對象的引用,用于解決循環(huán)依賴。 * 對于單例 bean,該變量一般為 true。更詳細(xì)的解釋可以參考我之前的文章 */ boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // ☆ 添加 bean 工廠對象到 singletonFactories 緩存中 addSingletonFactory(beanName, new ObjectFactory
上面的代碼簡化了不少,不過看起來仍有點(diǎn)復(fù)雜。好在,上面代碼的主線邏輯比較簡單,由三個方法組成。如下:
1. 創(chuàng)建原始 bean 實(shí)例 → createBeanInstance(beanName, mbd, args) 2. 添加原始對象工廠對象到 singletonFactories 緩存中 → addSingletonFactory(beanName, new ObjectFactory
到這里,本節(jié)涉及到的源碼就分析完了。可是看完源碼后,我們似乎仍然不知道這些源碼是如何解決循環(huán)依賴問題的。難道本篇文章就到這里了嗎?答案是否。下面我來解答這個問題,這里我還是以 BeanA 和 BeanB 兩個類相互依賴為例。在上面的方法調(diào)用中,有幾個關(guān)鍵的地方,下面一一列舉出來:
1. 創(chuàng)建原始 bean 對象
instanceWrapper = createBeanInstance(beanName, mbd, args); final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
假設(shè) beanA 先被創(chuàng)建,創(chuàng)建后的原始對象為 BeanA@1234,上面代碼中的 bean 變量指向就是這個對象。
2. 暴露早期引用
addSingletonFactory(beanName, new ObjectFactory
beanA 指向的原始對象創(chuàng)建好后,就開始把指向原始對象的引用通過 ObjectFactory 暴露出去。getEarlyBeanReference 方法的第三個參數(shù) bean 指向的正是 createBeanInstance 方法創(chuàng)建出原始 bean 對象 BeanA@1234。
3. 解析依賴
populateBean(beanName, mbd, instanceWrapper);
populateBean 用于向 beanA 這個原始對象中填充屬性,當(dāng)它檢測到 beanA 依賴于 beanB 時,會首先去實(shí)例化 beanB。beanB 在此方法處也會解析自己的依賴,當(dāng)它檢測到 beanA 這個依賴,于是調(diào)用 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 調(diào)用 BeanFactry.getBean("beanA") 以獲取 beanB 的依賴。getBean("beanA") 會先調(diào)用 getSingleton("beanA"),嘗試從緩存中獲取 beanA。此時由于 beanA 還沒完全實(shí)例化好,于是 this.singletonObjects.get("beanA") 返回 null。接著 this.earlySingletonObjects.get("beanA") 也返回空,因為 beanA 早期引用還沒放入到這個緩存中。最后調(diào)用 singletonFactory.getObject() 返回 singletonObject,此時 singletonObject != null。singletonObject 指向 BeanA@1234,也就是 createBeanInstance 創(chuàng)建的原始對象。此時 beanB 獲取到了這個原始對象的引用,beanB 就能順利完成實(shí)例化。beanB 完成實(shí)例化后,beanA 就能獲取到 beanB 所指向的實(shí)例,beanA 隨之也完成了實(shí)例化工作。由于 beanB.beanA 和 beanA 指向的是同一個對象 BeanA@1234,所以 beanB 中的 beanA 此時也處于可用狀態(tài)了。
以上的過程對應(yīng)下面的流程圖:
4. 總結(jié)到這里,本篇文章差不多就快寫完了,不知道大家看懂了沒。這篇文章在前面做了大量的鋪墊,然后再進(jìn)行源碼分析。相比于我之前寫的幾篇文章,本篇文章所對應(yīng)的源碼難度上比之前簡單一些。但說實(shí)話也不好寫,我本來只想簡單介紹一下背景知識,然后直接進(jìn)行源碼分析。但是又怕有的朋友看不懂,所以還是用了大篇幅介紹的背景知識。這樣寫,可能有的朋友覺得比較啰嗦。但是考慮到大家的水平不一,為了保證讓大家能夠更好的理解,所以還是盡量寫的詳細(xì)一點(diǎn)。本篇文章總的來說寫的還是有點(diǎn)累的,花了一些心思思考怎么安排章節(jié)順序,怎么簡化代碼和畫圖。如果大家看完這篇文章,覺得還不錯的話,不妨給個贊吧,也算是對我的鼓勵吧。
由于個人的技術(shù)能力有限,若文章有錯誤不妥之處,歡迎大家指出來。好了,本篇文章到此結(jié)束,謝謝大家的閱讀。
參考:《Spring 源碼深度解析》- 郝佳
附錄:Spring 源碼分析文章列表 Ⅰ. IOC更新時間 | 標(biāo)題 |
---|---|
2018-05-30 | Spring IOC 容器源碼分析系列文章導(dǎo)讀 |
2018-06-01 | Spring IOC 容器源碼分析 - 獲取單例 bean |
2018-06-04 | Spring IOC 容器源碼分析 - 創(chuàng)建單例 bean 的過程 |
2018-06-06 | Spring IOC 容器源碼分析 - 創(chuàng)建原始 bean 對象 |
2018-06-08 | Spring IOC 容器源碼分析 - 循環(huán)依賴的解決辦法 |
2018-06-11 | Spring IOC 容器源碼分析 - 填充屬性到 bean 原始對象 |
2018-06-11 | Spring IOC 容器源碼分析 - 余下的初始化工作 |
更新時間 | 標(biāo)題 |
---|---|
2018-06-17 | Spring AOP 源碼分析系列文章導(dǎo)讀 |
2018-06-20 | Spring AOP 源碼分析 - 篩選合適的通知器 |
2018-06-20 | Spring AOP 源碼分析 - 創(chuàng)建代理對象 |
2018-06-22 | Spring AOP 源碼分析 - 攔截器鏈的執(zhí)行過程 |
更新時間 | 標(biāo)題 |
---|---|
2018-06-29 | Spring MVC 原理探秘 - 一個請求的旅行過程 |
2018-06-30 | Spring MVC 原理探秘 - 容器的創(chuàng)建過程 |
本文在知識共享許可協(xié)議 4.0 下發(fā)布,轉(zhuǎn)載需在明顯位置處注明出處
作者:coolblog.xyz
本文同步發(fā)布在我的個人博客:http://www.coolblog.xyz
本作品采用知識共享署名-非商業(yè)性使用-禁止演繹 4.0 國際許可協(xié)議進(jìn)行許可。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/69693.html
摘要:本文是容器源碼分析系列文章的第一篇文章,將會著重介紹的一些使用方法和特性,為后續(xù)的源碼分析文章做鋪墊。我們可以通過這兩個別名獲取到這個實(shí)例,比如下面的測試代碼測試結(jié)果如下本小節(jié),我們來了解一下這個特性。 1. 簡介 Spring 是一個輕量級的企業(yè)級應(yīng)用開發(fā)框架,于 2004 年由 Rod Johnson 發(fā)布了 1.0 版本。經(jīng)過十幾年的迭代,現(xiàn)在的 Spring 框架已經(jīng)非常成熟了...
摘要:簡介為了寫容器源碼分析系列的文章,我特地寫了一篇容器的導(dǎo)讀文章。在做完必要的準(zhǔn)備工作后,從本文開始,正式開始進(jìn)入源碼分析的階段。從緩存中獲取單例。返回以上就是和兩個方法的分析。 1. 簡介 為了寫 Spring IOC 容器源碼分析系列的文章,我特地寫了一篇 Spring IOC 容器的導(dǎo)讀文章。在導(dǎo)讀一文中,我介紹了 Spring 的一些特性以及閱讀 Spring 源碼的一些建議。在...
摘要:關(guān)于創(chuàng)建實(shí)例的過程,我將會分幾篇文章進(jìn)行分析。源碼分析創(chuàng)建實(shí)例的入口在正式分析方法前,我們先來看看方法是在哪里被調(diào)用的。時,表明方法不存在,此時拋出異常。該變量用于表示是否提前暴露單例,用于解決循環(huán)依賴。 1. 簡介 在上一篇文章中,我比較詳細(xì)的分析了獲取 bean 的方法,也就是getBean(String)的實(shí)現(xiàn)邏輯。對于已實(shí)例化好的單例 bean,getBean(String) ...
摘要:簡介本篇文章是容器源碼分析系列文章的最后一篇文章,本篇文章所分析的對象是方法,該方法用于對已完成屬性填充的做最后的初始化工作。后置處理器是拓展點(diǎn)之一,通過實(shí)現(xiàn)后置處理器接口,我們就可以插手的初始化過程。 1. 簡介 本篇文章是Spring IOC 容器源碼分析系列文章的最后一篇文章,本篇文章所分析的對象是 initializeBean 方法,該方法用于對已完成屬性填充的 bean 做最...
摘要:在寫完容器源碼分析系列文章中的最后一篇后,沒敢懈怠,趁熱打鐵,花了天時間閱讀了方面的源碼。從今天開始,我將對部分的源碼分析系列文章進(jìn)行更新。全稱是,即面向切面的編程,是一種開發(fā)理念。在中,切面只是一個概念,并沒有一個具體的接口或類與此對應(yīng)。 1. 簡介 前一段時間,我學(xué)習(xí)了 Spring IOC 容器方面的源碼,并寫了數(shù)篇文章對此進(jìn)行講解。在寫完 Spring IOC 容器源碼分析系列...
閱讀 2097·2023-04-26 00:09
閱讀 3115·2021-09-26 10:12
閱讀 3481·2019-08-30 15:44
閱讀 2863·2019-08-30 13:47
閱讀 922·2019-08-23 17:56
閱讀 3226·2019-08-23 15:31
閱讀 475·2019-08-23 13:47
閱讀 2508·2019-08-23 11:56