經(jīng)典面試題:Spring 如何解決循環(huán)依賴(lài)的問(wèn)題
最近我不是把Spring 的源碼過(guò)了一遍嘛,在整理Spring相關(guān)的八股文的時(shí)候,想到以前沒(méi)怎么搞明白的面試題在梳理梳理了一下,終于搞懂了分享給大家!有需要筆記的小伙伴加我wx我發(fā)給你......
? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

Spring 如何解決循環(huán)依賴(lài)的問(wèn)題
(一)Spring ?IOC容器---對(duì)象循環(huán)依賴(lài)
什么是循環(huán)依賴(lài)?what?
(1)循環(huán)依賴(lài)-->循環(huán)引用。--->即2個(gè)或以上bean 互相持有對(duì)方,最終形成閉環(huán)。eg:A依賴(lài)B,B依賴(lài)C,C又依賴(lài)A。
【注意:這里不是函數(shù)的循環(huán)調(diào)用【是個(gè)死循環(huán),除非有終結(jié)條件】,是對(duì)象相互依賴(lài)關(guān)系】

Spring中循環(huán)依賴(lài)的場(chǎng)景?where?
①:構(gòu)造器的循環(huán)依賴(lài)?!具@個(gè)Spring解決不了】
StudentA有參構(gòu)造是StudentB。StudentB的有參構(gòu)造是StudentC,StudentC的有參構(gòu)造是StudentA ,這樣就產(chǎn)生了一個(gè)循環(huán)依賴(lài)的情況,
我們都把這三個(gè)Bean交給Spring管理,并用有參構(gòu)造實(shí)例化
XML
測(cè)試:
報(bào)錯(cuò):出現(xiàn)一下的報(bào)錯(cuò)信息就對(duì)了說(shuō)明出現(xiàn)了循環(huán)依賴(lài)問(wèn)題?
②【setter循環(huán)依賴(lài)】field屬性的循環(huán)依賴(lài)【setter方式 單例,默認(rèn)方式-->通過(guò)遞歸方法找出當(dāng)前Bean所依賴(lài)的Bean,然后提前緩存【會(huì)放入Cach中】起來(lái)。通過(guò)提前暴露 -->暴露一個(gè)exposedObject用于返回提前暴露的Bean?!?br>
setter方式注入:

圖中前兩步驟得知:Spring是先將Bean對(duì)象實(shí)例化【依賴(lài)無(wú)參構(gòu)造函數(shù)】--->再設(shè)置對(duì)象屬性的
這就不會(huì)報(bào)錯(cuò)了:
原因:Spring先用構(gòu)造器實(shí)例化Bean對(duì)象----->將實(shí)例化結(jié)束的對(duì)象放到一個(gè)Map中,并且Spring提供獲取這個(gè)未設(shè)置屬性的實(shí)例化對(duì)象的引用方法。結(jié)合我們的實(shí)例來(lái)看,,當(dāng)Spring實(shí)例化了StudentA、StudentB、StudentC后,緊接著會(huì)去設(shè)置對(duì)象的屬性,此時(shí)StudentA依賴(lài)StudentB,就會(huì)去Map中取出存在里面的單例StudentB對(duì)象,以此類(lèi)推,不會(huì)出來(lái)循環(huán)的問(wèn)題嘍。
如何檢測(cè)是否有循環(huán)依賴(lài)?how to ?find?
可以 Bean在創(chuàng)建的時(shí)候給其打個(gè)標(biāo)記,如果遞歸調(diào)用回來(lái)發(fā)現(xiàn)正在創(chuàng)建中的話--->即可說(shuō)明循環(huán)依賴(lài)。
4.怎么解決的?todo what?
Spring的循環(huán)依賴(lài)的理論依據(jù)其實(shí)是基于Java的引用傳遞,當(dāng)我們獲取到對(duì)象的引用時(shí),對(duì)象的field或zh屬性是可以延后設(shè)置的(但是構(gòu)造器必須是在獲取引用之前)。
Spring的單例對(duì)象的初始化主要分為三步:

①:createBeanInstance:實(shí)例化,其實(shí)也就是 調(diào)用對(duì)象的構(gòu)造方法實(shí)例化對(duì)象
②:populateBean:填充屬性,這一步主要是多bean的依賴(lài)屬性進(jìn)行填充
③:initializeBean:調(diào)用spring xml中的init() 方法。
從上面講述的單例bean初始化步驟我們可以知道,循環(huán)依賴(lài)主要發(fā)生在第一、第二步。也就是構(gòu)造器循環(huán)依賴(lài)和field循環(huán)依賴(lài)。
那么我們要解決循環(huán)引用也應(yīng)該從初始化過(guò)程著手,對(duì)于單例來(lái)說(shuō),在Spring容器整個(gè)生命周期內(nèi),有且只有一個(gè)對(duì)象,所以很容易想到這個(gè)對(duì)象應(yīng)該存在Cache中,Spring為了解決單例的循環(huán)依賴(lài)問(wèn)題,使用了三級(jí)緩存。調(diào)整配置文件,將構(gòu)造函數(shù)注入方式改為 屬性注入方式 即可
3.源碼怎么實(shí)現(xiàn)的?how?
(1)三級(jí)緩存源碼主要 指:
這三級(jí)緩存分別指:
singletonFactories :?jiǎn)卫龑?duì)象工廠的cache earlySingletonObjects :提前暴光的單例對(duì)象的Cache 。【用于檢測(cè)循環(huán)引用,與singletonFactories互斥】 singletonObjects:?jiǎn)卫龑?duì)象的cache
我們?cè)趧?chuàng)建bean的時(shí)候,首先想到的是從cache中獲取這個(gè)單例的bean,這個(gè)緩存就是singletonObjects。主要調(diào)用方法就就是:
上面的代碼需要解釋兩個(gè)參數(shù):
● isSingletonCurrentlyInCreation()判斷當(dāng)前單例bean是否正在創(chuàng)建中,也就是沒(méi)有初始化完成(比如A的構(gòu)造器依賴(lài)了B對(duì)象所以得先去創(chuàng)建B對(duì)象, 或則在A的populateBean過(guò)程中依賴(lài)了B對(duì)象,得先去創(chuàng)建B對(duì)象,這時(shí)的A就是處于創(chuàng)建中的狀態(tài)。)
● allowEarlyReference 是否允許從singletonFactories中通過(guò)getObject拿到對(duì)象 分析getSingleton()的整個(gè)過(guò)程,Spring首先從一級(jí)緩存singletonObjects中獲取。如果獲取不到,并且對(duì)象正在創(chuàng)建中,就再?gòu)亩?jí)緩存earlySingletonObjects中獲取。如果還是獲取不到且允許singletonFactories通過(guò)getObject()獲取,就從三級(jí)緩存singletonFactory.getObject()(三級(jí)緩存)獲取,如果獲取到了則:
這個(gè)接口在下面被引用
注意:
Assert.notNull(temp,"temp的值不能為空");
判斷傳進(jìn)來(lái)的參數(shù)值是否不為空值,如果為空就拋出異常throw new IllegalArgumentException(msg),代碼如果不捕捉處理這個(gè)異常,代碼不往下執(zhí)行,不為空代碼繼續(xù)向下執(zhí)行。
這里就是解決循環(huán)依賴(lài)的關(guān)鍵,這段代碼發(fā)生在createBeanInstance之后,也就是說(shuō)單例對(duì)象此時(shí)已經(jīng)被創(chuàng)建出來(lái)(調(diào)用了構(gòu)造器)。這個(gè)對(duì)象已經(jīng)被生產(chǎn)出來(lái)了,雖然還不完美(還沒(méi)有進(jìn)行初始化的第二步和第三步),但是已經(jīng)能被人認(rèn)出來(lái)了(根據(jù)對(duì)象引用能定位到堆中的對(duì)象),所以Spring此時(shí)將這個(gè)對(duì)象提前曝光出來(lái)讓大家認(rèn)識(shí),讓大家使用。
總結(jié)?
這樣做有什么好處呢?讓我們來(lái)分析一下“A的某個(gè)field或者setter依賴(lài)了B的實(shí)例對(duì)象,同時(shí)B的某個(gè)field或者setter依賴(lài)了A的實(shí)例對(duì)象”這種循環(huán)依賴(lài)的情況。A首先完成了初始化的第一步,并且將自己提前曝光到singletonFactories中,此時(shí)進(jìn)行初始化的第二步,發(fā)現(xiàn)自己依賴(lài)對(duì)象B,此時(shí)就嘗試去get(B),發(fā)現(xiàn)B還沒(méi)有被create,所以走create流程,B在初始化第一步的時(shí)候發(fā)現(xiàn)自己依賴(lài)了對(duì)象A,于是嘗試get(A),嘗試一級(jí)緩存singletonObjects(肯定沒(méi)有,因?yàn)锳還沒(méi)初始化完全),嘗試二級(jí)緩存earlySingletonObjects(也沒(méi)有),嘗試三級(jí)緩存singletonFactories,由于A通過(guò)ObjectFactory將自己提前曝光了,所以B能夠通過(guò)ObjectFactory.getObject拿到A對(duì)象(雖然A還沒(méi)有初始化完全,但是總比沒(méi)有好呀),B拿到A對(duì)象后順利完成了初始化階段1、2、3,完全初始化之后將自己放入到一級(jí)緩存singletonObjects中。此時(shí)返回A中,A此時(shí)能拿到B的對(duì)象順利完成自己的初始化階段2、3,最終A也完成了初始化,進(jìn)去了一級(jí)緩存singletonObjects中,而且更加幸運(yùn)的是,由于B拿到了A的對(duì)象引用,所以B現(xiàn)在hold住的A對(duì)象完成了初始化。
知道了這個(gè)原理時(shí)候,肯定就知道為啥Spring不能解決“A的構(gòu)造方法中依賴(lài)了B的實(shí)例對(duì)象,同時(shí)B的構(gòu)造方法中依賴(lài)了A的實(shí)例對(duì)象”這類(lèi)問(wèn)題了!因?yàn)榧尤雜ingletonFactories三級(jí)緩存的前提是執(zhí)行了構(gòu)造器,所以構(gòu)造器的循環(huán)依賴(lài)沒(méi)法解決

最后,歡迎學(xué)編程的朋友們加入我的 《java學(xué)習(xí)群》,我會(huì) 1 對(duì) 1 解決你的問(wèn)題(盡我所能),群里的人也會(huì)對(duì)解答疑惑。

掃碼拉群,學(xué)習(xí)打卡,交流經(jīng)驗(yàn)