Spring源碼初探-IOC(4)-Bean的初始化-循環(huán)依賴的解決

前言

在實(shí)際工作中饲趋,經(jīng)常由于設(shè)計(jì)不佳或者各種因素狠角,導(dǎo)致類之間相互依賴驱还。這些類可能單獨(dú)使用時(shí)不會(huì)出問(wèn)題膊毁,但是在使用Spring進(jìn)行管理的時(shí)候可能就會(huì)拋出BeanCurrentlyInCreationException等異常 渊鞋。當(dāng)拋出這種異常時(shí)表示Spring解決不了該循環(huán)依賴绰更,本文將簡(jiǎn)要說(shuō)明Spring對(duì)于循環(huán)依賴的解決方法。

循環(huán)依賴的產(chǎn)生和解決的前提

循環(huán)依賴的產(chǎn)生可能有很多種情況锡宋,例如:

A的構(gòu)造方法中依賴了B的實(shí)例對(duì)象儡湾,同時(shí)B的構(gòu)造方法中依賴了A的實(shí)例對(duì)象

A的構(gòu)造方法中依賴了B的實(shí)例對(duì)象,同時(shí)B的某個(gè)field或者setter需要A的實(shí)例對(duì)象执俩,以及反之

A的某個(gè)field或者setter依賴了B的實(shí)例對(duì)象徐钠,同時(shí)B的某個(gè)field或者setter依賴了A的實(shí)例對(duì)象,以及反之

當(dāng)然役首,Spring對(duì)于循環(huán)依賴的解決不是無(wú)條件的尝丐,首先前提條件是針對(duì)scope單例并且沒(méi)有顯式指明不需要解決循環(huán)依賴的對(duì)象,而且要求該對(duì)象沒(méi)有被代理過(guò)宋税。同時(shí)Spring解決循環(huán)依賴也不是萬(wàn)能摊崭,以上三種情況只能解決兩種,第一種在構(gòu)造方法中相互依賴的情況Spring也無(wú)力回天杰赛。結(jié)論先給在這呢簸,下面來(lái)看看Spring的解決方法,知道了解決方案就能明白為啥第一種情況無(wú)法解決了。

Spring對(duì)于循環(huán)依賴的解決

Spring循環(huán)依賴的理論依據(jù)其實(shí)是Java基于引用傳遞根时,當(dāng)我們獲取到對(duì)象的引用時(shí)瘦赫,對(duì)象的field或者或?qū)傩允强梢匝雍笤O(shè)置的。

Spring單例對(duì)象的初始化其實(shí)可以分為三步:

createBeanInstance蛤迎, 實(shí)例化确虱,實(shí)際上就是調(diào)用對(duì)應(yīng)的構(gòu)造方法構(gòu)造對(duì)象,此時(shí)只是調(diào)用了構(gòu)造方法替裆,spring xml中指定的property并沒(méi)有進(jìn)行populate

populateBean校辩,填充屬性,這步對(duì)spring xml中指定的property進(jìn)行populate

initializeBean辆童,調(diào)用spring xml中指定的init方法宜咒,或者AfterPropertiesSet方法

會(huì)發(fā)生循環(huán)依賴的步驟集中在第一步和第二步。

三級(jí)緩存

對(duì)于單例對(duì)象來(lái)說(shuō)把鉴,在Spring的整個(gè)容器的生命周期內(nèi)故黑,有且只存在一個(gè)對(duì)象,很容易想到這個(gè)對(duì)象應(yīng)該存在Cache中庭砍,Spring大量運(yùn)用了Cache的手段场晶,在循環(huán)依賴問(wèn)題的解決過(guò)程中甚至使用了“三級(jí)緩存”。

“三級(jí)緩存”主要是指

/** Cache of singleton objects: bean name --> bean instance */private finalMap singletonObjects =newConcurrentHashMap(256);/** Cache of singleton factories: bean name --> ObjectFactory */private finalMap> singletonFactories =newHashMap>(16);/** Cache of early singleton objects: bean name --> bean instance */private finalMap earlySingletonObjects =newHashMap(16);

從字面意思來(lái)說(shuō):singletonObjects指單例對(duì)象的cache怠缸,singletonFactories指單例對(duì)象工廠的cache诗轻,earlySingletonObjects指提前曝光的單例對(duì)象的cache。以上三個(gè)cache構(gòu)成了三級(jí)緩存揭北,Spring就用這三級(jí)緩存巧妙的解決了循環(huán)依賴問(wèn)題概耻。

解決方法

回想上篇文章中關(guān)于Bean創(chuàng)建的過(guò)程,首先Spring會(huì)嘗試從緩存中獲取罐呼,這個(gè)緩存就是指singletonObjects鞠柄,主要調(diào)用的方法是:

protectedObjectgetSingleton(String beanName,booleanallowEarlyReference){? Object singletonObject =this.singletonObjects.get(beanName);if(singletonObject ==null&& isSingletonCurrentlyInCreation(beanName)) {synchronized(this.singletonObjects) {? ? ? ? singletonObject =this.earlySingletonObjects.get(beanName);if(singletonObject ==null&& allowEarlyReference) {? ? ? ? ? ? ObjectFactory singletonFactory =this.singletonFactories.get(beanName);if(singletonFactory !=null) {? ? ? ? ? ? ? singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);? ? ? ? ? ? }? ? ? ? }? ? ? }? }return(singletonObject != NULL_OBJECT ? singletonObject :null);}

首先解釋兩個(gè)參數(shù):

isSingletonCurrentlyInCreation 判斷對(duì)應(yīng)的單例對(duì)象是否在創(chuàng)建中,當(dāng)單例對(duì)象沒(méi)有被初始化完全(例如A定義的構(gòu)造函數(shù)依賴了B對(duì)象嫉柴,得先去創(chuàng)建B對(duì)象厌杜,或者在populatebean過(guò)程中依賴了B對(duì)象,得先去創(chuàng)建B對(duì)象计螺,此時(shí)A處于創(chuàng)建中)

allowEarlyReference 是否允許從singletonFactories中通過(guò)getObject拿到對(duì)象

分析getSingleton的整個(gè)過(guò)程夯尽,Spring首先從singletonObjects(一級(jí)緩存)中嘗試獲取,如果獲取不到并且對(duì)象在創(chuàng)建中登馒,則嘗試從earlySingletonObjects(二級(jí)緩存)中獲取匙握,如果還是獲取不到并且允許從singletonFactories通過(guò)getObject獲取,則通過(guò)singletonFactory.getObject()(三級(jí)緩存)獲取陈轿。如果獲取到了則

this.earlySingletonObjects.put(beanName,singletonObject);this.singletonFactories.remove(beanName);

則移除對(duì)應(yīng)的singletonFactory,將singletonObject放入到earlySingletonObjects圈纺,其實(shí)就是將三級(jí)緩存提升到二級(jí)緩存中秦忿!

Spring解決循環(huán)依賴的訣竅就在于singletonFactories這個(gè)cache,這個(gè)cache中存的是類型為ObjectFactory蛾娶,其定義如下:

publicinterfaceObjectFactory{TgetObject()throwsBeansException;}

在bean創(chuàng)建過(guò)程中灯谣,有兩處比較重要的匿名內(nèi)部類實(shí)現(xiàn)了該接口。一處是

newObjectFactory() {@OverridepublicObjectgetObject()throwsBeansException{try{returncreateBean(beanName, mbd, args);? ? ? }catch(BeansException ex) {? ? ? ? destroySingleton(beanName);throwex;? ? ? }? }

在上文已經(jīng)提到蛔琅,Spring利用其創(chuàng)建bean(這樣做真的很不明確呀...)

另一處就是:

addSingletonFactory(beanName,newObjectFactory() {@OverridepublicObjectgetObject()throwsBeansException{returngetEarlyBeanReference(beanName, mbd, bean);? }});

此處就是解決循環(huán)依賴的關(guān)鍵胎许,這段代碼發(fā)生在createBeanInstance之后,也就是說(shuō)單例對(duì)象此時(shí)已經(jīng)被創(chuàng)建出來(lái)的罗售。這個(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也完成了初始化,長(zhǎng)大成人肺樟,進(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ì)象”這類問(wèn)題了硬爆!

總結(jié)

Spring通過(guò)三級(jí)緩存加上“提前曝光”機(jī)制欣舵,配合Java的對(duì)象引用原理,比較完美地解決了某些情況下的循環(huán)依賴問(wèn)題缀磕!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缘圈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子袜蚕,更是在濱河造成了極大的恐慌糟把,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件牲剃,死亡現(xiàn)場(chǎng)離奇詭異遣疯,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)凿傅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門缠犀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人聪舒,你說(shuō)我怎么就攤上這事辨液。” “怎么了箱残?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵室梅,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我疚宇,道長(zhǎng)亡鼠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任敷待,我火速辦了婚禮间涵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘榜揖。我一直安慰自己勾哩,他們只是感情好抗蠢,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著思劳,像睡著了一般迅矛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上潜叛,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天秽褒,我揣著相機(jī)與錄音,去河邊找鬼威兜。 笑死销斟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的椒舵。 我是一名探鬼主播蚂踊,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼笔宿!你這毒婦竟也來(lái)了犁钟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤泼橘,失蹤者是張志新(化名)和其女友劉穎涝动,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體侥加,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年粪躬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了担败。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡镰官,死狀恐怖提前,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情泳唠,我是刑警寧澤狈网,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站笨腥,受9級(jí)特大地震影響拓哺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜脖母,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一士鸥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧谆级,春花似錦烤礁、人聲如沸讼积。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)勤众。三九已至,卻和暖如春鲤脏,著一層夾襖步出監(jiān)牢的瞬間们颜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工凑兰, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掌桩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓姑食,卻偏偏與公主長(zhǎng)得像波岛,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子音半,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容