Spring三級(jí)緩存解決循環(huán)依賴的過(guò)程分析
循環(huán)依賴
什么是循環(huán)依賴?很簡(jiǎn)單,看下方的代碼就知曉了
@Service public class A { @Autowired private B b; } @Service public class B { @Autowired private A a; }
上面這兩種方式都是循環(huán)依賴,應(yīng)該很好理解,當(dāng)然也可以是三個(gè) Bean 甚至更多的 Bean 相互依賴,原理都是一樣的,今天我們主要分析兩個(gè) Bean 的依賴。
這種循環(huán)依賴可能會(huì)產(chǎn)生問(wèn)題,例如 A 要依賴 B,發(fā)現(xiàn) B 還沒(méi)創(chuàng)建。
于是開(kāi)始創(chuàng)建 B ,創(chuàng)建的過(guò)程發(fā)現(xiàn) B 要依賴 A, 而 A 還沒(méi)創(chuàng)建好呀,因?yàn)樗?B 創(chuàng)建好。
就這樣它們倆就擱這卡 bug 了。
解決思路
上面這種循環(huán)依賴在實(shí)際場(chǎng)景中是會(huì)出現(xiàn)的,所以 Spring 需要解決這個(gè)問(wèn)題,那如何解決呢?
關(guān)鍵就是提前暴露未完全創(chuàng)建完畢的 Bean。
在 Spring 中,只有同時(shí)滿足以下兩點(diǎn)才能解決循環(huán)依賴的問(wèn)題:
依賴的 Bean 必須都是單例依賴注入的方式,必須不全是構(gòu)造器注入,且 beanName 字母序在前的不能是構(gòu)造器注入
Spring 只支持單例的循環(huán)依賴,因?yàn)槿绻麅蓚€(gè) Bean 都是原型模式的話:
創(chuàng)建 A1 需要?jiǎng)?chuàng)建一個(gè) B1。
創(chuàng)建 B1 的時(shí)候要?jiǎng)?chuàng)建一個(gè) A2。
創(chuàng)建 A2 又要?jiǎng)?chuàng)建一個(gè) B2。
創(chuàng)建 B2 又要?jiǎng)?chuàng)建一個(gè) A3。
創(chuàng)建 A3 又要?jiǎng)?chuàng)建一個(gè) B3…
就又卡 BUG 了,是吧,因?yàn)樵湍J蕉夹枰獎(jiǎng)?chuàng)建新的對(duì)象,不能跟用以前的對(duì)象。
如果是單例的話,創(chuàng)建 A 需要?jiǎng)?chuàng)建 B,而創(chuàng)建的 B 需要的是之前的個(gè) A, 不然就不叫單例了,對(duì)吧?
也是基于這點(diǎn), Spring 就能操作操作了。
具體做法就是:先創(chuàng)建 A,此時(shí)的 A 是不完整的(沒(méi)有注入 B),用個(gè) map 保存這個(gè)不完整的 A,再創(chuàng)建 B ,B 需要 A。
所以從那個(gè) map 得到“不完整”的 A,此時(shí)的 B 就完整了,然后 A 就可以注入 B,然后 A 就完整了,B 也完整了,且它們是相互依賴的。
那為啥必須不全是構(gòu)造器注入,因?yàn)樵?Spring 中創(chuàng)建 Bean 分三步:
- 實(shí)例化,createBeanInstance,就是 new 了個(gè)對(duì)象
- 屬性注入,populateBean, 就是 set 一些屬性值
- 初始化,initializeBean,執(zhí)行一些 aware 接口中的方法,initMethod,AOP代理等
明確了上面這三點(diǎn),再結(jié)合我上面說(shuō)的“不完整的”,我們來(lái)理一下。
如果全是構(gòu)造器注入,比如A(B b),那表明在 new 的時(shí)候,就需要得到 B,此時(shí)需要 new B 。
但是 B 也是要在構(gòu)造的時(shí)候注入 A ,即B(A a),這時(shí)候 B 需要在一個(gè) map 中找到不完整的 A ,發(fā)現(xiàn)找不到。
為什么找不到?因?yàn)?A 還沒(méi) new 完呢,所以找到不完整的 A,因此如果全是構(gòu)造器注入的話,那么 Spring 無(wú)法處理循環(huán)依賴。
解決流程
經(jīng)過(guò)上面的鋪墊,我想你對(duì) Spring 如何解決循環(huán)依賴應(yīng)該已經(jīng)有點(diǎn)感覺(jué)了,接下來(lái)我們就來(lái)看看它到底是如何實(shí)現(xiàn)的。
明確了 Spring 創(chuàng)建 Bean 的三步驟之后,我們?cè)賮?lái)看看它為單例搞的三個(gè) map:
- 一級(jí)緩存,singletonObjects,存儲(chǔ)所有已創(chuàng)建完畢的單例 Bean (完整的 Bean)
- 二級(jí)緩存,earlySingletonObjects,存儲(chǔ)所有僅完成實(shí)例化,但還未進(jìn)行屬性注入和初始化的 Bean
- 三級(jí)緩存,singletonFactories,存儲(chǔ)能建立這個(gè) Bean 的一個(gè)工廠,通過(guò)工廠能獲取這個(gè) Bean,延遲化 Bean 的生成,工廠生成的 Bean 會(huì)塞入二級(jí)緩存
這三個(gè) map 是如何配合的呢?
- 首先,獲取單例 Bean 的時(shí)候會(huì)通過(guò) BeanName 先去 singletonObjects(一級(jí)緩存) 查找完整的 Bean,如果找到則直接返回,否則進(jìn)行步驟 2。
- 看對(duì)應(yīng)的 Bean 是否在創(chuàng)建中,如果不在直接返回找不到,如果是,則會(huì)去 earlySingletonObjects (二級(jí)緩存)查找 Bean,如果找到則返回,否則進(jìn)行步驟 3
- 去 singletonFactories (三級(jí)緩存)通過(guò) BeanName 查找到對(duì)應(yīng)的工廠,如果存著工廠則通過(guò)工廠創(chuàng)建 Bean ,并且放置到 earlySingletonObjects 中。
- 如果三個(gè)緩存都沒(méi)找到,則返回 null。
從上面的步驟我們可以得知,如果查詢發(fā)現(xiàn) Bean 還未創(chuàng)建,到第二步就直接返回 null,不會(huì)繼續(xù)查二級(jí)和三級(jí)緩存。
返回 null 之后,說(shuō)明這個(gè) Bean 還未創(chuàng)建,這個(gè)時(shí)候會(huì)標(biāo)記這個(gè) Bean 正在創(chuàng)建中,然后再調(diào)用 createBean 來(lái)創(chuàng)建 Bean,而實(shí)際創(chuàng)建是調(diào)用方法 doCreateBean。
doCreateBean 這個(gè)方法就會(huì)執(zhí)行上面我們說(shuō)的三步驟:
- 實(shí)例化
- 屬性注入
- 初始化
以上面的例子來(lái)講解,在實(shí)例化A 之后,會(huì)往三級(jí)緩存 singletonFactories 塞入一個(gè)工廠A,而調(diào)用這個(gè)工廠A的 getObject 方法,就能得到這個(gè) A。
要注意,此時(shí) Spring 是不知道會(huì)不會(huì)有循環(huán)依賴發(fā)生的,但是它不管,反正往 singletonFactories 塞這個(gè)工廠,這里就是提前暴露。
然后就開(kāi)始執(zhí)行屬性注入,這個(gè)時(shí)候 A 發(fā)現(xiàn)需要注入 B,所以去 getBean(B),此時(shí)又會(huì)走一遍上面描述的邏輯,到了 B 的屬性注入這一步。
此時(shí) B 調(diào)用 getBean(A),這時(shí)候一級(jí)緩存里面找不到,但是發(fā)現(xiàn) A 正在創(chuàng)建中的,于是去二級(jí)緩存找,發(fā)現(xiàn)沒(méi)找到,于是去三級(jí)緩存找,然后找到了。
并且通過(guò)上面提前在三級(jí)緩存里暴露的工廠得到 A,然后將這個(gè)工廠從三級(jí)緩存里刪除,并將 A 加入到二級(jí)緩存中。
然后結(jié)果就是 B 屬性注入成功。
緊接著 B 調(diào)用 initializeBean 初始化,最終返回,此時(shí) B 已經(jīng)被加到了一級(jí)緩存里 。
這時(shí)候就回到了 A 的屬性注入,此時(shí)注入了 B,接著執(zhí)行初始化,最后 A 也會(huì)被加到一級(jí)緩存里,且從二級(jí)緩存中刪除 A。
Spring 解決依賴循環(huán)就是按照上面所述的邏輯來(lái)實(shí)現(xiàn)的。
重點(diǎn)就是在對(duì)象實(shí)例化之后,都會(huì)在三級(jí)緩存里加入一個(gè)工廠,提前對(duì)外暴露還未完整的 Bean,這樣如果被循環(huán)依賴了,對(duì)方就可以利用這個(gè)工廠得到一個(gè)不完整的 Bean,破壞了循環(huán)的條件。
二個(gè)緩存不行?
上面都說(shuō)了那么多了,那我們思考下,解決循環(huán)依賴需要三級(jí)緩存嗎?
很明顯,如果僅僅只是為了破解循環(huán)依賴,二個(gè)緩存夠了,壓根就不必要三級(jí)。
你思考一下,在實(shí)例化 Bean A 之后,我在二級(jí) map 里面塞入這個(gè) A,然后繼續(xù)屬性注入。
發(fā)現(xiàn) A 依賴 B 所以要?jiǎng)?chuàng)建 Bean B,這時(shí)候 B 就能從二級(jí) map 得到 A ,完成 B 的建立之后, A 自然而然能完成。
所以為什么要搞個(gè)三級(jí)緩存,且里面存的是創(chuàng)建 Bean 的工廠呢?
我們來(lái)看下調(diào)用工廠的 getObject 到底會(huì)做什么,實(shí)際會(huì)調(diào)用下面這個(gè)方法:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }
重點(diǎn)就在中間的判斷,如果 false,返回就是參數(shù)傳進(jìn)來(lái)的 bean,沒(méi)任何變化。
如果是 true 說(shuō)明有 InstantiationAwareBeanPostProcessors 。
且循環(huán)的是 smartInstantiationAware 類型,如有這個(gè) BeanPostProcessor 說(shuō)明 Bean 需要被 aop 代理。
我們都知道如果有代理的話,那么我們想要直接拿到的是代理對(duì)象。
也就是說(shuō)如果 A 需要被代理,那么 B 依賴的 A 是已經(jīng)被代理的 A,所以我們不能返回 A 給 B,而是返回代理的 A 給 B。
這個(gè)工廠的作用就是判斷這個(gè)對(duì)象是否需要代理,如果否則直接返回,如果是則返回代理對(duì)象。
看到這明白的小伙伴肯定會(huì)問(wèn),那跟三級(jí)緩存有什么關(guān)系,我可以在要放到二級(jí)緩存的時(shí)候判斷這個(gè) Bean 是否需要代理,如果要直接放代理的對(duì)象不就完事兒了。
是的,這個(gè)思路看起來(lái)沒(méi)任何問(wèn)題,問(wèn)題就出在時(shí)機(jī),這跟 Bean 的生命周期有關(guān)系。
Spring 原本的設(shè)計(jì)是,bean 的創(chuàng)建過(guò)程分三個(gè)階段:
1 創(chuàng)建實(shí)例 createBeanInstance – 創(chuàng)建出 bean 的原始對(duì)象
2 填充依賴 populateBean – 利用反射,使用 BeanWrapper 來(lái)設(shè)置屬性值
3 initializeBean – 執(zhí)行 bean 創(chuàng)建后的處理,包括 AOP 對(duì)象的產(chǎn)生
在沒(méi)有循環(huán)依賴的場(chǎng)景下:第 1,2 步都是 bean 的原始對(duì)象,第 3 步 initializeBean 時(shí),才會(huì)生成 AOP 代理對(duì)象。
所以,循環(huán)依賴打破了 AOP 代理 bean 生成的時(shí)機(jī),需要在 populateBean 之前就生成 AOP 代理 bean。
而且,生成 AOP 代理需要執(zhí)行 BeanPostProcessor,而 Spring 原本的設(shè)計(jì)是在第 3 步 initializeBean 時(shí)才去調(diào)用 BeanPostProcessor 的。
所以 Spring 先在一個(gè)三級(jí)緩存放置一個(gè)工廠,用于代表Bean的引用。只有在上面的第三步時(shí),才會(huì)通過(guò)這個(gè)工廠去創(chuàng)建代理對(duì)象,這樣生命周期就不會(huì)亂套了。
理論上來(lái)說(shuō),使用二級(jí)緩存是可以解決 AOP 代理 bean 的循環(huán)依賴的。只是 Spring 沒(méi)有選擇這樣去實(shí)現(xiàn)。Spring 選擇了三級(jí)緩存來(lái)實(shí)現(xiàn),讓 bean 的創(chuàng)建流程更加符合常理,更加清晰明了。
總結(jié)
好了,看到這里想必你應(yīng)該對(duì) Spring 的循環(huán)依賴很清晰了,并且面試的時(shí)候肯定也難不倒你了。
我稍微總結(jié)下:
- 有構(gòu)造器注入,不一定會(huì)產(chǎn)生問(wèn)題,具體得看是否都是構(gòu)造器注和 BeanName 的字母序
- 如果單純?yōu)榱舜蚱蒲h(huán)依賴,不需要三級(jí)緩存,兩級(jí)就夠了。
- 三級(jí)緩存是否為延遲代理的創(chuàng)建,盡量不打破 Bean 的生命周期
到此這篇關(guān)于Spring三級(jí)緩存解決循環(huán)依賴的文章就介紹到這了,更多相關(guān)Spring循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring中的三級(jí)緩存與循環(huán)依賴詳解
- Spring的循環(huán)依賴、三級(jí)緩存解決方案源碼詳細(xì)解析
- Spring通過(guò)三級(jí)緩存解決循環(huán)依賴問(wèn)題的過(guò)程詳解
- Java中通過(guò)三級(jí)緩存解決Spring循環(huán)依賴詳解
- Spring三級(jí)緩存思想解決循環(huán)依賴總結(jié)分析
- 一文教你如何通過(guò)三級(jí)緩存解決Spring循環(huán)依賴
- 教你Spring如何使用三級(jí)緩存解決循環(huán)依賴
- 一篇文章帶你理解Java Spring三級(jí)緩存和循環(huán)依賴
- Spring為什么要用三級(jí)緩存解決循環(huán)依賴呢
相關(guān)文章
mybatis plus開(kāi)發(fā)過(guò)程中遇到的問(wèn)題記錄及解決
這篇文章主要介紹了mybatis plus開(kāi)發(fā)過(guò)程中遇到的問(wèn)題記錄及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07解決IDEA中不能正常輸入光標(biāo)變粗的問(wèn)題
這篇文章主要介紹了在IDEA中不能正常輸入光標(biāo)變粗的解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-09-09Java Chassis3注冊(cè)中心分區(qū)隔離技術(shù)解密
這篇文章主要為大家介紹了Java Chassis3注冊(cè)中心分區(qū)隔離技術(shù)解密,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01Mybatis-plus與Mybatis依賴沖突問(wèn)題解決方法
,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧這篇文章主要介紹了Mybatis-plus與Mybatis依賴沖突問(wèn)題解決方法2021-04-04Java中StringUtils工具類進(jìn)行String為空的判斷解析
這篇文章主要介紹了Java中StringUtils工具類進(jìn)行String為空的判斷解析,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01