电竞比分网-中国电竞赛事及体育赛事平台

分享

Spring循環(huán)依賴及解決方式

 印度阿三17 2020-03-15

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

循環(huán)依賴其實(shí)就是循環(huán)引用,也就是兩個(gè)或者兩個(gè)以上的bean互相持有對(duì)方,最終形成閉環(huán)。比如A依賴于B,B依賴于C,C又依賴于A。如下圖:注意,這里不是函數(shù)的循環(huán)調(diào)用,是對(duì)象的相互依賴關(guān)系。循環(huán)調(diào)用其實(shí)就是一個(gè)死循環(huán),除非有終結(jié)條件。 Spring中循環(huán)依賴場(chǎng)景有:  (1)構(gòu)造器的循環(huán)依賴  (2)field屬性的循環(huán)依賴 其中,構(gòu)造器的循環(huán)依賴問(wèn)題無(wú)法解決,只能拋出BeanCurrentlyInCreationException異常,在解決屬性循環(huán)依賴時(shí),spring采用的是提前暴露對(duì)象的方法。

2. 怎么檢測(cè)是否存在循環(huán)依賴

檢測(cè)循環(huán)依賴相對(duì)比較容易,Bean在創(chuàng)建的時(shí)候可以給該Bean打標(biāo),如果遞歸調(diào)用回來(lái)發(fā)現(xiàn)正在創(chuàng)建中的話,即說(shuō)明了循環(huán)依賴了。

3. Spring怎么解決循環(huán)依賴

Spring的循環(huán)依賴的理論依據(jù)基于Java的引用傳遞,當(dāng)獲得對(duì)象的引用時(shí),對(duì)象的屬性是可以延后設(shè)置的。(但是構(gòu)造器必須是在獲取引用之前) Spring的單例對(duì)象的初始化主要分為三步:   (1)createBeanInstance:實(shí)例化,其實(shí)也就是調(diào)用對(duì)象的構(gòu)造方法實(shí)例化對(duì)象 (2)populateBean:填充屬性,這一步主要是多bean的依賴屬性進(jìn)行填充 (3)initializeBean:調(diào)用spring xml中的init 方法。 從上面單例bean的初始化可以知道:循環(huán)依賴主要發(fā)生在第一、二步,也就是構(gòu)造器循環(huán)依賴和field循環(huán)依賴。那么我們要解決循環(huán)引用也應(yīng)該從初始化過(guò)程著手,對(duì)于單例來(lái)說(shuō),在Spring容器整個(gè)生命周期內(nèi),有且只有一個(gè)對(duì)象,所以很容易想到這個(gè)對(duì)象應(yīng)該存在Cache中,Spring為了解決單例的循環(huán)依賴問(wèn)題,使用了三級(jí)緩存。 這三級(jí)緩存分別指:  singletonFactories : 單例對(duì)象工廠的cache  earlySingletonObjects :提前暴光的單例對(duì)象的Cache  singletonObjects:?jiǎn)卫龑?duì)象的cache 在創(chuàng)建bean的時(shí)候,首先想到的是從cache中獲取這個(gè)單例的bean,這個(gè)緩存就是singletonObjects。如果獲取不到,并且對(duì)象正在創(chuàng)建中,就再?gòu)亩?jí)緩存earlySingletonObjects中獲取。如果還是獲取不到且允許singletonFactories通過(guò)getObject()獲取,就從三級(jí)緩存singletonFactory.getObject()(三級(jí)緩存)獲取,如果獲取到了則:從singletonFactories中移除,并放入earlySingletonObjects中。其實(shí)也就是從三級(jí)緩存移動(dòng)到了二級(jí)緩存。 從上面三級(jí)緩存的分析,我們可以知道,Spring解決循環(huán)依賴的訣竅就在于singletonFactories這個(gè)三級(jí)cache。這個(gè)cache的類(lèi)型是ObjectFactory。這里就是解決循環(huán)依賴的關(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í),讓大家使用。 這樣做有什么好處呢?讓我們來(lái)分析一下“A的某個(gè)field或者setter依賴了B的實(shí)例對(duì)象,同時(shí)B的某個(gè)field或者setter依賴了A的實(shí)例對(duì)象”這種循環(huán)依賴的情況。A首先完成了初始化的第一步,并且將自己提前曝光到singletonFactories中,此時(shí)進(jìn)行初始化的第二步,發(fā)現(xiàn)自己依賴對(duì)象B,此時(shí)就嘗試去get(B),發(fā)現(xiàn)B還沒(méi)有被create,所以走create流程,B在初始化第一步的時(shí)候發(fā)現(xiàn)自己依賴了對(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)造方法中依賴了B的實(shí)例對(duì)象,同時(shí)B的構(gòu)造方法中依賴了A的實(shí)例對(duì)象”這類(lèi)問(wèn)題了!因?yàn)榧尤雜ingletonFactories三級(jí)緩存的前提是執(zhí)行了構(gòu)造器,所以構(gòu)造器的循環(huán)依賴沒(méi)法解決。

4.基于構(gòu)造器的循環(huán)依賴

Spring容器會(huì)將每一個(gè)正在創(chuàng)建的Bean 標(biāo)識(shí)符放在一個(gè)“當(dāng)前創(chuàng)建Bean池”中,Bean標(biāo)識(shí)符在創(chuàng)建過(guò)程中將一直保持在這個(gè)池中,因此如果在創(chuàng)建Bean過(guò)程中發(fā)現(xiàn)自己已經(jīng)在“當(dāng)前創(chuàng)建Bean池”里時(shí)將拋出BeanCurrentlyInCreationException異常表示循環(huán)依賴;而對(duì)于創(chuàng)建完畢的Bean將從“當(dāng)前創(chuàng)建Bean池”中清除掉。 Spring容器先創(chuàng)建單例A,A依賴B,然后將A放在“當(dāng)前創(chuàng)建Bean池”中,此時(shí)創(chuàng)建B,B依賴C ,然后將B放在“當(dāng)前創(chuàng)建Bean池”中,此時(shí)創(chuàng)建C,C又依賴A, 但是,此時(shí)A已經(jīng)在池中,所以會(huì)報(bào)錯(cuò),,因?yàn)樵诔刂械腂ean都是未初始化完的,所以會(huì)依賴錯(cuò)誤 ,(初始化完的Bean會(huì)從池中移除)

5.基于setter屬性的循環(huán)依賴

我們結(jié)合上面那張圖看,Spring先是用構(gòu)造實(shí)例化Bean對(duì)象 ,創(chuàng)建成功后,Spring會(huì)通過(guò)以下代碼提前將對(duì)象暴露出來(lái),此時(shí)的對(duì)象A還沒(méi)有完成屬性注入,屬于早期對(duì)象,此時(shí)Spring會(huì)將這個(gè)實(shí)例化結(jié)束的對(duì)象放到一個(gè)Map中,并且Spring提供了獲取這個(gè)未設(shè)置屬性的實(shí)例化對(duì)象引用的方法。 結(jié)合我們的實(shí)例來(lái)看,當(dāng)Spring實(shí)例化了A、B、C后,緊接著會(huì)去設(shè)置對(duì)象的屬性,此時(shí)A依賴B,就會(huì)去Map中取出存在里面的單例B對(duì)象,以此類(lèi)推,不會(huì)出來(lái)循環(huán)的問(wèn)題嘍

6.結(jié)束語(yǔ)

不要使用基于構(gòu)造函數(shù)的依賴注入,可以通過(guò)以下方式解決: 1.在字段上使用@Autowired注解,讓Spring決定在合適的時(shí)機(jī)注入 2.用基于setter方法的依賴注入。   參考文章:https://blog.csdn.net/chejinqiang/article/details/80003868來(lái)源:https://www./content-4-659801.html

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多