Java中的Spring循環(huán)依賴詳情
一、什么是循環(huán)依賴?
很簡(jiǎn)單,就是A對(duì)象依賴了B對(duì)象,B對(duì)象依賴了A對(duì)象。
比如:
那么循環(huán)依賴是個(gè)問題嗎?
如果不考慮Spring,循環(huán)依賴并不是問題,因?yàn)閷?duì)象之間相互依賴是很正常的事情。
比如:
這樣,A,B就依賴上了。
但是,在Spring中循環(huán)依賴就是一個(gè)問題了,為什么? 因?yàn)?,在Spring中,一個(gè)對(duì)象并不是簡(jiǎn)單new出來了,而是會(huì)經(jīng)過一系列的Bean的生命周期,就是因?yàn)锽ean的生命周期所以才會(huì)出現(xiàn)循環(huán)依賴問題。當(dāng)然,在Spring中,出現(xiàn)循環(huán)依賴的場(chǎng)景很多,有的場(chǎng)景Spring自動(dòng)幫我們解決了,而有的場(chǎng)景則需要程序員來解決,下文詳細(xì)來說。
要明白Spring中的循環(huán)依賴,得先明白Spring中Bean的生命周期。
二、Bean的生命周期
這里不會(huì)對(duì)Bean的生命周期進(jìn)行詳細(xì)的描述,只描述一下大概的過程。
Bean的生命周期指的就是:在Spring中,Bean是如何生成的?
被Spring管理的對(duì)象叫做Bean。Bean的生成步驟如下:
- 1、Spring掃描class得到BeanDefinition
- 2、根據(jù)得到的BeanDefinition去生成bean
- 3、首先根據(jù)class推斷構(gòu)造方法
- 4、根據(jù)推斷出來的構(gòu)造方法,反射,得到一個(gè)對(duì)象(暫時(shí)叫做原始對(duì)象)
- 5、填充原始對(duì)象中的屬性(依賴注入)
- 6、如果原始對(duì)象中的某個(gè)方法被AOP了,那么則需要根據(jù)原始對(duì)象生成一個(gè)代理對(duì)象
- 7、把最終生成的代理對(duì)象放入單例池(源碼中叫做singletonObjects)中,下次getBean時(shí)就直接從單例池拿即可
可以看到,對(duì)于Spring中的Bean的生成過程,步驟還是很多的,并且不僅僅只有上面的7步,還有很多很多,比如Aware回調(diào)、初始化等等,這里不詳細(xì)討論。
可以發(fā)現(xiàn),在Spring中,構(gòu)造一個(gè)Bean,包括了new這個(gè)步驟(第4步構(gòu)造方法反射)。 得到一個(gè)原始對(duì)象后,Spring需要給對(duì)象中的屬性進(jìn)行依賴注入,那么這個(gè)注入過程是怎樣的?
比如上文說的A類,A類中存在一個(gè)B類的b屬性,所以,當(dāng)A類生成了一個(gè)原始對(duì)象之后,就會(huì)去給b屬性去賦值,此時(shí)就會(huì)根據(jù)b屬性的類型和屬性名去BeanFactory
中去獲取B類所對(duì)應(yīng)的單例bean。如果此時(shí)BeanFactory
中存在B對(duì)應(yīng)的Bean,那么直接拿來賦值給b屬性;如果此時(shí)BeanFactory中不存在B對(duì)應(yīng)的Bean,則需要生成一個(gè)B對(duì)應(yīng)的Bean,然后賦值給b屬性。
問題就出現(xiàn)在第二種情況,如果此時(shí)B類在BeanFactory
中還沒有生成對(duì)應(yīng)的Bean,那么就需要去生成,就會(huì)經(jīng)過B的Bean的生命周期。
那么在創(chuàng)建B類的Bean的過程中,如果B類中存在一個(gè)A類的a屬性,那么在創(chuàng)建B的Bean的過程中就需要A類對(duì)應(yīng)的Bean,但是,觸發(fā)B類Bean的創(chuàng)建的條件是A類Bean在創(chuàng)建過程中的依賴注入,所以這里就出現(xiàn)了循環(huán)依賴:
ABean創(chuàng)建–>依賴了B屬性–>觸發(fā)BBean創(chuàng)建—>B依賴了A屬性—>需要ABean(但ABean還在創(chuàng)建過程中)
從而導(dǎo)致ABean創(chuàng)建不出來,BBean也創(chuàng)建不出來。
這是循環(huán)依賴的場(chǎng)景,但是上文說了,在Spring中,通過某些機(jī)制幫開發(fā)者解決了部分循環(huán)依賴的問題,這個(gè)機(jī)制就是三級(jí)緩存。
三、三級(jí)緩存
三級(jí)緩存是通用的叫法。
- 一級(jí)緩存為:singletonObjects
- 二級(jí)緩存為:earlySingletonObjects
- 三級(jí)緩存為:singletonFactories
先稍微解釋一下這三個(gè)緩存的作用,后面詳細(xì)分析:
- • singletonObjects中緩存的是已經(jīng)經(jīng)歷了完整生命周期的bean對(duì)象。
- • earlySingletonObjects比singletonObjects多了一個(gè)early,表示緩存的是早期的bean對(duì)象。早期是什么意思?表示Bean的生命周期還沒走完就把這個(gè)Bean放入了earlySingletonObjects。
- • singletonFactories中緩存的是ObjectFactory,表示對(duì)象工廠,用來創(chuàng)建某個(gè)對(duì)象的。
解決循環(huán)依賴思路分析
先來分析為什么緩存能解決循環(huán)依賴。
上文分析得到,之所以產(chǎn)生循環(huán)依賴的問題,主要是:
A創(chuàng)建時(shí)—>需要B---->B去創(chuàng)建—>需要A,從而產(chǎn)生了循環(huán)
那么如何打破這個(gè)循環(huán),加個(gè)中間人(緩存)
A的Bean在創(chuàng)建過程中,在進(jìn)行依賴注入之前,先把A的原始Bean放入緩存(提早暴露,只要放到緩存了,其他Bean需要時(shí)就可以從緩存中拿了),放入緩存后,再進(jìn)行依賴注入,此時(shí)A的Bean依賴了B的Bean,如果B的Bean不存在,則需要?jiǎng)?chuàng)建B的Bean,而創(chuàng)建B的Bean的過程和A一樣,也是先創(chuàng)建一個(gè)B的原始對(duì)象,然后把B的原始對(duì)象提早暴露出來放入緩存中,然后在對(duì)B的原始對(duì)象進(jìn)行依賴注入A,此時(shí)能從緩存中拿到A的原始對(duì)象(雖然是A的原始對(duì)象,還不是最終的Bean),B的原始對(duì)象依賴注入完了之后,B的生命周期結(jié)束,那么A的生命周期也能結(jié)束。
因?yàn)檎麄€(gè)過程中,都只有一個(gè)A原始對(duì)象,所以對(duì)于B而言,就算在屬性注入時(shí),注入的是A原始對(duì)象,也沒有關(guān)系,因?yàn)锳原始對(duì)象在后續(xù)的生命周期中在堆中沒有發(fā)生變化。 從上面這個(gè)分析過程中可以得出,只需要一個(gè)緩存就能解決循環(huán)依賴了,那么為什么Spring中還需要singletonFactories
呢?
這是難點(diǎn),基于上面的場(chǎng)景想一個(gè)問題:如果A的原始對(duì)象注入給B的屬性之后,A的原始對(duì)象進(jìn)行了AOP產(chǎn)生了一個(gè)代理對(duì)象,此時(shí)就會(huì)出現(xiàn),對(duì)于A而言,它的Bean對(duì)象其實(shí)應(yīng)該是AOP之后的代理對(duì)象,而B的a屬性對(duì)應(yīng)的并不是AOP之后的代理對(duì)象,這就產(chǎn)生了沖突。
B依賴的A和最終的A不是同一個(gè)對(duì)象。
那么如何解決這個(gè)問題?這個(gè)問題可以說沒有辦法解決。
因?yàn)樵谝粋€(gè)Bean的生命周期最后,Spring提供了BeanPostProcessor
可以去對(duì)Bean進(jìn)行加工,這個(gè)加工不僅僅只是能修改Bean的屬性值,也可以替換掉當(dāng)前Bean。
舉個(gè)例子:
運(yùn)行main方法,得到的打印如下:
所以在BeanPostProcessor
中可以完全替換掉某個(gè)beanName對(duì)應(yīng)的bean對(duì)象。
而BeanPostProcessor的執(zhí)行在Bean的生命周期中是處于屬性注入之后的,循環(huán)依賴是發(fā)生在屬性注入過程中的,所以很有可能導(dǎo)致,**注入給B對(duì)象的A對(duì)象和經(jīng)歷過完整生命周期之后的A對(duì)象,不是一個(gè)對(duì)象。**這就是有問題的。
所以在這種情況下的循環(huán)依賴,Spring是解決不了的,因?yàn)樵趯傩宰⑷霑r(shí),Spring也不知道A對(duì)象后續(xù)會(huì)經(jīng)過哪些BeanPostProcessor以及會(huì)對(duì)A對(duì)象做什么處理。
四、Spring到底解決了哪種情況下的循環(huán)依賴
雖然上面的情況可能發(fā)生,但是肯定發(fā)生得很少,我們通常在開發(fā)過程中,不會(huì)這樣去做,但是,某個(gè)beanName對(duì)應(yīng)的最終對(duì)象和原始對(duì)象不是一個(gè)對(duì)象卻會(huì)經(jīng)常出現(xiàn),這就是AOP。
AOP就是通過一個(gè)BeanPostProcessor
來實(shí)現(xiàn)的,這個(gè)BeanPostProcessor
就是AnnotationAwareAspectJAutoProxyCreator
,它的父類是AbstractAutoProxyCreator
,而在Spring中AOP利用的要么是JDK動(dòng)態(tài)代理,要么CGLib的動(dòng)態(tài)代理,所以如果給一個(gè)類中的某個(gè)方法設(shè)置了切面,那么這個(gè)類最終就需要生成一個(gè)代理對(duì)象。
一般過程就是:A類—>生成一個(gè)普通對(duì)象–>屬性注入–>基于切面生成一個(gè)代理對(duì)象–>把代理對(duì)象放入singletonObjects單例池中。
而AOP可以說是Spring中除開IOC的另外一大功能,而循環(huán)依賴又是屬于IOC范疇的,所以這兩大功能想要并存,Spring需要特殊處理。
如何處理的,就是利用了第三級(jí)緩存singletonFactories
。
首先,singletonFactories中存的是某個(gè)beanName對(duì)應(yīng)的ObjectFactory,在bean的生命周期中,生成完原始對(duì)象之后,就會(huì)構(gòu)造一個(gè)ObjectFactory存入singletonFactories中。這個(gè)ObjectFactory是一個(gè)函數(shù)式接口,所以支持Lambda表達(dá)式:() -> getEarlyBeanReference(beanName, mbd, bean)
上面的Lambda表達(dá)式就是一個(gè)ObjectFactory,執(zhí)行該Lambda表達(dá)式就會(huì)去執(zhí)行getEarlyBeanReference
方法
而該方法如下:
該方法會(huì)去執(zhí)行SmartInstantiationAwareBeanPostProcessor
中的getEarlyBeanReference
方法,而這個(gè)接口下的實(shí)現(xiàn)類中只有兩個(gè)類實(shí)現(xiàn)了這個(gè)方法,一個(gè)是AbstractAutoProxyCreator
,一個(gè)是InstantiationAwareBeanPostProcessorAdapter
,
它的實(shí)現(xiàn)如下:
所以很明顯,在整個(gè)Spring
中,默認(rèn)就只有AbstractAutoProxyCreator
真正意義上實(shí)現(xiàn)了getEarlyBeanReference
方法,而該類就是用來進(jìn)行AOP的。上文提到的AnnotationAwareAspectJAutoProxyCreator
的父類就是AbstractAutoProxyCreator
。
那么getEarlyBeanReference方法到底在干什么?
首先得到一個(gè)cachekey,cachekey就是beanName。
然后把beanName和bean(這是原始對(duì)象)存入earlyProxyReferences
中 調(diào)用wrapIfNecessary進(jìn)行AOP,得到一個(gè)代理對(duì)象。
那么,什么時(shí)候會(huì)調(diào)用getEarlyBeanReference方法呢?回到循環(huán)依賴的場(chǎng)景中
左邊文字: 這個(gè)ObjectFactory就是上文說的labmda表達(dá)式,中間有g(shù)etEarlyBeanReference方法,注意存入singletonFactories時(shí)并不會(huì)執(zhí)行l(wèi)ambda表達(dá)式,也就是不會(huì)執(zhí)行g(shù)etEarlyBeanReference方法
右邊文字: 從singletonFactories
根據(jù)beanName得到一個(gè)ObjectFactory,然后執(zhí)行ObjectFactory,也就是執(zhí)行getEarlyBeanReference
方法,此時(shí)會(huì)得到一個(gè)A原始對(duì)象經(jīng)過AOP之后的代理對(duì)象,然后把該代理對(duì)象放入earlySingletonObjects中,注意此時(shí)并沒有把代理對(duì)象放入singletonObjects中,那什么時(shí)候放入到singletonObjects中呢?
我們這個(gè)時(shí)候得來理解一下earlySingletonObjects
的作用,此時(shí),我們只得到了A原始對(duì)象的代理對(duì)象,這個(gè)對(duì)象還不完整,因?yàn)锳原始對(duì)象還沒有進(jìn)行屬性填充,所以此時(shí)不能直接把A的代理對(duì)象放入singletonObjects中,所以只能把代理對(duì)象放入earlySingletonObjects,假設(shè)現(xiàn)在有其他對(duì)象依賴了A,那么則可以從earlySingletonObjects
中得到A原始對(duì)象的代理對(duì)象了,并且是A的同一個(gè)代理對(duì)象。
當(dāng)B創(chuàng)建完了之后,A繼續(xù)進(jìn)行生命周期,而A在完成屬性注入后,會(huì)按照它本身的邏輯去進(jìn)行AOP,而此時(shí)我們知道A原始對(duì)象已經(jīng)經(jīng)歷過了AOP,所以對(duì)于A本身而言,不會(huì)再去進(jìn)行AOP了,那么怎么判斷一個(gè)對(duì)象是否經(jīng)歷過了AOP呢?會(huì)利用上文提到的earlyProxyReferences
,在AbstractAutoProxyCreator的postProcessAfterInitialization方法中,會(huì)去判斷當(dāng)前beanName是否在earlyProxyReferences,如果在則表示已經(jīng)提前進(jìn)行過AOP了,無需再次進(jìn)行AOP。
對(duì)于A而言,進(jìn)行了AOP的判斷后,以及BeanPostProcessor的執(zhí)行之后,就需要把A對(duì)應(yīng)的對(duì)象放入singletonObjects
中了,但是我們知道,應(yīng)該是要A的代理對(duì)象放入singletonObjects中,所以此時(shí)需要從earlySingletonObjects中得到代理對(duì)象,然后入singletonObjects中。
整個(gè)循環(huán)依賴解決完畢。
五、總結(jié)
至此,總結(jié)一下三級(jí)緩存:
- 1、singletonObjects:緩存某個(gè)
beanName
對(duì)應(yīng)的經(jīng)過了完整生命周期的bean - 2、earlySingletonObjects:緩存提前拿原始對(duì)象進(jìn)行了AOP之后得到的代理對(duì)象,原始對(duì)象還沒有進(jìn)行屬性注入和后續(xù)的
BeanPostProcessor
等生命周期 - 3、singletonFactories:緩存的是一個(gè)ObjectFactory,主要用來去生成原始對(duì)象進(jìn)行了AOP之后得到的代理對(duì)象,在每個(gè)Bean的生成過程中,都會(huì)提前暴露一個(gè)工廠,這個(gè)工廠可能用到,也可能用不到,如果沒有出現(xiàn)循環(huán)依賴依賴本bean,那么這個(gè)工廠無用,本bean按照自己的生命周期執(zhí)行,執(zhí)行完后直接把本bean放入
singletonObjects
中即可,如果出現(xiàn)了循環(huán)依賴依賴了本bean,則另外那個(gè)bean執(zhí)行ObjectFactory提交得到一個(gè)AOP之后的代理對(duì)象(如果有AOP的話,如果無需AOP,則直接得到一個(gè)原始對(duì)象)。 - 4、其實(shí)還要一個(gè)緩存,就是
earlyProxyReferences
,它用來記錄某個(gè)原始對(duì)象是否進(jìn)行過AOP了。
到此這篇關(guān)于Java中的Spring循環(huán)依賴詳情的文章就介紹到這了,更多相關(guān)Java 循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java class文件格式之方法_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了Java class文件格式之方法的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-06-06分布式面試分布式鎖實(shí)現(xiàn)及應(yīng)用場(chǎng)景
這篇文章主要為大家介紹了關(guān)于分布式的面試問題,分布式鎖的實(shí)現(xiàn)及應(yīng)用不同場(chǎng)景下的使用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03Java上轉(zhuǎn)型和下轉(zhuǎn)型對(duì)象
這篇文章給大家講述了Java上轉(zhuǎn)型和下轉(zhuǎn)型對(duì)象的詳細(xì)用法以及相關(guān)的代碼分享,有興趣的朋友可以學(xué)習(xí)下。2018-03-03