淺談Spring中的循環(huán)依賴

什么是循環(huán)依賴?

????之前提到了在進(jìn)行創(chuàng)建單例Bean的時候有個類參數(shù)singletonCurrentlyInCreation,這個參數(shù)是用來記錄當(dāng)前正在進(jìn)行實例化的beanName。此外還定義了三個存放不同實例的緩存:

? ? 一級緩存:singletonObjects,用來存放以beanName為key,對應(yīng)已完成bean生命周期的bean實例作為value;

? ? 二級緩存:earlySingletonObjects,里面存放的都是提前暴露的單例實例,此時bean的生命周期還未結(jié)束;

? ? 三級緩存:singletonFactories:用來存放以beanName為key,對應(yīng)的BeanFactory為value;

? ? 我們先創(chuàng)建如圖的兩個類,除了各自引用外再添加一個參數(shù),用來跟蹤這個bean的生命周期進(jìn)行到哪一步了,并重寫toString()方法,如圖1、圖2:

圖1
圖2

? ? 執(zhí)行結(jié)果如圖3,但是按照我們寫的自定義Spring容器,會出現(xiàn)A和B不斷的相互引用,最終耗盡了服務(wù)器資源,這就是循環(huán)依賴,Spring就是利用上面幾個參數(shù)來解決循環(huán)依賴的問題。

圖3

Spring如何解決循環(huán)依賴?

? ? 1、在getBean操作的時候,進(jìn)入doGetBean方法,執(zhí)行g(shù)etSingleton(beanName)方法,來嘗試從三個緩存中拿到對應(yīng)的實例,A_BeanName剛進(jìn)來的時候三個緩存中都沒有A_BeanName的值,在singletonCurrentlyInCreation中也沒有正在實例化的beanName,說明目前這個bean還未被創(chuàng)建,如圖4:

圖4

? ? 2、由于循環(huán)依賴只發(fā)生在單例的情況,那么直接看圖5:

圖5

????先關(guān)注下getSingleton操作,和上面獲取實例的只是個同名方法而已,進(jìn)來一共可以分成四部分,如圖6:”beforeSingletonCreation(創(chuàng)建實例之前的操作)、getObject(創(chuàng)建實例)、afterSingletonCreation(創(chuàng)建實例之后的操作)、addSingleton(如果是新生成的單例,把他加進(jìn)緩存)。beforeSingletonCreation會把當(dāng)前的beanName放進(jìn)singletonCurrentlyInCreation中,如圖7。在調(diào)用getObject的時候會調(diào)用到匿名類的createBean方法來創(chuàng)建實例。

圖6
圖7

? ? 3、關(guān)注doCreateBean,首先調(diào)用createBeanInstance,本次采用的是無參構(gòu)造方法instantiateBean,把A包裝成A_BeanWrapper,具體的封裝情況參考上節(jié)的getBean流程。通過斷點可以看到此時A_BeanWrapper已經(jīng)包含了B,但是沒有A的其他屬性,B屬性也為null,說明A并沒有創(chuàng)建完成,此時B進(jìn)行未進(jìn)行依賴注入,如圖8:

圖8

? ? 4、earlySingletonExposure參數(shù)代表是否能進(jìn)行提前暴露,也可以從這看到提前暴露的條件:單例、允許循環(huán)依賴、當(dāng)前beanName正處于正在創(chuàng)建Bean的列表中。如圖9:

圖9

????此時的getEarlyBeanReference方法是用來收集BeanFactory實例的,從實現(xiàn)方法里的getCacheKey中可以看到,工廠Bean前綴特有的&標(biāo)識,如圖10和圖11:

圖10
圖11

????然后調(diào)用addSingletonFactory往三級緩存中添加A_BeanName和A_BeanFactory,如圖12:

圖12

? ? 5、通過populateBean進(jìn)行依賴注入,此時又是一個BeanPostProcessor的應(yīng)用,postProcessProperties方法對@Autowired方法進(jìn)行掃描,如圖13:

圖13

????findAutowiringMetadata返回的InjectionMetadata參數(shù)中的injectedElements參數(shù)就是B,如圖14:

圖14

????繼續(xù)調(diào)用其inject方法,關(guān)注element.inject方法,由于@Autowired注解是放在參數(shù)上的,因此訪問AutowiredFieldElement中的inject方法,調(diào)用resolveFieldValue,到beanFactory.resolveDependency,到doResolveDependency,到findAutowireCandidates方法獲取到持有@Autowired注解的B,除了往autowiredBeanNames中添加對應(yīng)的beanName外,調(diào)用descripter.resolveCandidate方法,進(jìn)去發(fā)現(xiàn)是一個B_BeanName的getBean操作,如圖15、16:

圖15
圖16

? ? 6:、和A一樣,重復(fù)之前的實例化流程,在singletonCurrentlyInCreation中添加B_BeanName,通過addSingletonFactory往三級緩存中添加B_BeanName和B_BeanFactory,然后通過populateBean操作,最終通過getBean方法對A進(jìn)行實例化。

? ? 7、又再次來到了第一步的getSingleton方法,此時singletonCurrentlyInCreation中已經(jīng)有A_BeanName,雖然在一二級緩存中沒有對應(yīng)的實例,但是三次緩存中有A_BeanFactory,可以通過getObject拿到提前暴露的實例,然后把它放進(jìn)二級緩存,打斷點可以看到此時的A_SingletonObject中的B類屬性為null,如圖17::

圖17

????但是此時走的就不是else的路線了,關(guān)注getObjectForBeanInstance方法,最終也是返回一個對象實例,如果是工廠實例,則會調(diào)用其getObject方法生成對象實例返回,期間并沒有對其余屬性進(jìn)行諸如,我們在doGetBean的return的返回值中可以看到,如圖18:

圖18

? ? 8、步驟2進(jìn)行的getSingleton方法在getObject的方法終于有返回了,如圖19,返回的是B的實例,可以看到生成的B實例中已經(jīng)包含了testB屬性,以及提前暴露的A屬性。

圖19

????然后再進(jìn)行afterSingletonCreation方法,把B_BeanName從singletonCurrentlyInCreation中移除,如圖20:

圖20

????并通過addSingleton,把B_BeanName和B實例放入一級緩存后返回,此時A屬性中的B屬性依舊是null,如圖21:

圖21

? ? 9、從上面的流程看,B實例化是由A的實例化中B屬性的依賴注入觸發(fā)getBean完成的,現(xiàn)在B已經(jīng)被實例化了,因此A的B屬性也可以完成依賴注入了,這樣A中的B屬性也有值。由于B的屬性指向了A實例的堆空間,所以B類中的A屬性也被賦予值了,如圖22:

圖22

? ? 至此,Spring循環(huán)依賴的流程已經(jīng)結(jié)束了。

? ? 自定義Spring容器代碼地址:https://github.com/LuoChen1996/identitify_spring.git

? ? Spring源碼測試代碼地址:https://github.com/LuoChen1996/my_spring.git

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容