Spring-bean的循環(huán)依賴以及解決方式

本文主要是分析Spring bean的循環(huán)依賴,以及Spring的解決方式啥刻。 通過這種解決方式奸鸯,我們可以應用在我們實際開發(fā)項目中。

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

  2. 怎么檢測循環(huán)依賴

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

  4. Spring對于循環(huán)依賴無法解決的場景

  5. Spring解決循環(huán)依賴的方式我們能夠?qū)W到什么娄涩?

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

循環(huán)依賴其實就是循環(huán)引用,也就是兩個或則兩個以上的bean互相持有對方蓄拣,最終形成閉環(huán)扬虚。比如A依賴于B,B依賴于C球恤,C又依賴于A辜昵。如下圖:

循環(huán)依賴

注意,這里不是函數(shù)的循環(huán)調(diào)用咽斧,是對象的相互依賴關(guān)系堪置。循環(huán)調(diào)用其實就是一個死循環(huán),除非有終結(jié)條件张惹。
Spring中循環(huán)依賴場景有:

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

  2. field屬性的循環(huán)依賴

二. 怎么檢測是否存在循環(huán)依賴舀锨?

檢測循環(huán)依賴相對比較容易,Bean在創(chuàng)建的時候可以給該Bean打標宛逗,如果遞歸調(diào)用回來發(fā)現(xiàn)正在創(chuàng)建中的話坎匿,即說明了循環(huán)依賴了。

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

Spring的循環(huán)依賴的理論依據(jù)其實是基于Java的引用傳遞雷激,當我們獲取到對象的引用時替蔬,對象的field或則屬性是可以延后設(shè)置的(但是構(gòu)造器必須是在獲取引用之前)。

Spring的單例對象的初始化主要分為三步:

  1. createBeanInstance:實例化屎暇,其實也就是調(diào)用對象的構(gòu)造方法實例化對象

  2. populateBean:填充屬性承桥,這一步主要是多bean的依賴屬性進行填充

  3. initializeBean:調(diào)用spring xml中的init 方法。

bean初始化

從上面講述的單例bean初始化步驟我們可以知道根悼,循環(huán)依賴主要發(fā)生在第一快毛、第二步。也就是構(gòu)造器循環(huán)依賴和field循環(huán)依賴番挺。

那么我們要解決循環(huán)引用也應該從初始化過程著手唠帝,對于單例來說,在Spring容器整個生命周期內(nèi)玄柏,有且只有一個對象襟衰,所以很容易想到這個對象應該存在Cache中,Spring為了解決單例的循環(huán)依賴問題粪摘,使用了三級緩存瀑晒。

/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

這三級緩存分別指:

  • singletonObjects:單例對象的cache

  • earlySingletonObjects :提前暴光的單例對象的Cache

  • singletonFactories : 單例對象工廠的cache

我們在創(chuàng)建bean的時候,首先想到的是從cache中獲取這個單例的bean徘意,這個緩存就是singletonObjects苔悦。主要調(diào)用方法就就是:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        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;
}

上面的代碼需要解釋兩個參數(shù):

  • isSingletonCurrentlyInCreation():判斷當前單例bean是否正在創(chuàng)建中,也就是沒有初始化完成(比如A的構(gòu)造器依賴了B對象所以得先去創(chuàng)建B對象椎咧, 或則在A的populateBean過程中依賴了B對象玖详,得先去創(chuàng)建B對象把介,這時的A就是處于創(chuàng)建中的狀態(tài)。)

  • allowEarlyReference :是否允許從singletonFactories中通過getObject拿到對象

分析getSingleton()的整個過程蟋座,Spring首先從一級緩存singletonObjects中獲取拗踢。如果獲取不到,并且對象正在創(chuàng)建中向臀,就再從二級緩存earlySingletonObjects中獲取巢墅。如果還是獲取不到且允許singletonFactories通過getObject()獲取,就從三級緩存singletonFactory.getObject()(三級緩存)獲取券膀,如果獲取到了則:

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

從singletonFactories中移除君纫,并放入earlySingletonObjects中。其實也就是從三級緩存移動到了二級緩存芹彬。

從上面三級緩存的分析庵芭,我們可以知道,Spring解決循環(huán)依賴的訣竅就在于singletonFactories這個三級cache雀监。這個cache的類型是ObjectFactory,定義如下:

public interface ObjectFactory<T> {
    /**
     * Return an instance (possibly shared or independent)
     * of the object managed by this factory.
     * @return the resulting instance
     * @throws BeansException in case of creation errors
     */
    T getObject() throws BeansException;
}

這個接口在下面被引用

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

這里就是解決循環(huán)依賴的關(guān)鍵眨唬,這段代碼發(fā)生在createBeanInstance之后会前,也就是說單例對象此時已經(jīng)被創(chuàng)建出來(調(diào)用了構(gòu)造器)。這個對象已經(jīng)被生產(chǎn)出來了匾竿,雖然還不完美(還沒有進行初始化的第二步和第三步)瓦宜,但是已經(jīng)能被人認出來了(根據(jù)對象引用能定位到堆中的對象),所以Spring此時將這個對象提前曝光出來讓大家認識岭妖,讓大家使用临庇。

這樣做有什么好處呢?讓我們來分析一下“A的某個field或者setter依賴了B的實例對象昵慌,同時B的某個field或者setter依賴了A的實例對象”這種循環(huán)依賴的情況假夺。

A首先完成了初始化的第一步,并且將自己提前曝光到singletonFactories中斋攀,此時進行初始化的第二步已卷,發(fā)現(xiàn)自己依賴對象B,此時就嘗試去get(B)淳蔼,發(fā)現(xiàn)B還沒有被create侧蘸,所以走create流程,B在初始化第一步的時候發(fā)現(xiàn)自己依賴了對象A鹉梨,于是嘗試get(A)讳癌,嘗試一級緩存singletonObjects(肯定沒有,因為A還沒初始化完全)存皂,嘗試二級緩存earlySingletonObjects(也沒有)晌坤,嘗試三級緩存singletonFactories,由于A通過ObjectFactory將自己提前曝光了,所以B能夠通過ObjectFactory.getObject拿到A對象(雖然A還沒有初始化完全泡仗,但是總比沒有好呀)埋虹,B拿到A對象后順利完成了初始化階段1、2娩怎、3搔课,完全初始化之后將自己放入到一級緩存singletonObjects中。此時返回A中截亦,A此時能拿到B的對象順利完成自己的初始化階段2爬泥、3,最終A也完成了初始化崩瓤,進去了一級緩存singletonObjects中袍啡,而且更加幸運的是,由于B拿到了A的對象引用却桶,所以B現(xiàn)在hold住的A對象完成了初始化境输。

知道了這個原理時候,肯定就知道為啥Spring不能解決“A的構(gòu)造方法中依賴了B的實例對象颖系,同時B的構(gòu)造方法中依賴了A的實例對象”這類問題了嗅剖!因為加入singletonFactories三級緩存的前提是執(zhí)行了構(gòu)造器,所以構(gòu)造器的循環(huán)依賴沒法解決嘁扼。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末信粮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子趁啸,更是在濱河造成了極大的恐慌强缘,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件不傅,死亡現(xiàn)場離奇詭異旅掂,居然都是意外死亡,警方通過查閱死者的電腦和手機访娶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門辞友,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人震肮,你說我怎么就攤上這事称龙。” “怎么了戳晌?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵鲫尊,是天一觀的道長。 經(jīng)常有香客問我沦偎,道長疫向,這世上最難降的妖魔是什么咳蔚? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮搔驼,結(jié)果婚禮上谈火,老公的妹妹穿的比我還像新娘。我一直安慰自己舌涨,他們只是感情好糯耍,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著囊嘉,像睡著了一般温技。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扭粱,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天舵鳞,我揣著相機與錄音,去河邊找鬼琢蛤。 笑死蜓堕,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的博其。 我是一名探鬼主播套才,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼尖昏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了毫炉?” 一聲冷哼從身側(cè)響起柠硕,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎毫别,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡儿普,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了掷倔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眉孩。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖勒葱,靈堂內(nèi)的尸體忽然破棺而出浪汪,到底是詐尸還是另有隱情,我是刑警寧澤凛虽,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布死遭,位于F島的核電站,受9級特大地震影響凯旋,放射性物質(zhì)發(fā)生泄漏呀潭。R本人自食惡果不足惜钉迷,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钠署。 院中可真熱鬧糠聪,春花似錦、人聲如沸谐鼎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽该面。三九已至夭苗,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間隔缀,已是汗流浹背题造。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留猾瘸,地道東北人界赔。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像牵触,于是被迫代替她去往敵國和親淮悼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354