Spring 系列(七)循環(huán)依賴問題

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

循環(huán)依賴其實就是循環(huán)引用碉怔,也就是兩個或兩個以上的Bean互相持有對方鳞骤,最終形成閉環(huán)址儒。比如A依賴B,B依賴C允悦,C又依賴A。


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

  • 構(gòu)造器的循環(huán)依賴(構(gòu)造器注入)
  • Field屬性的循環(huán)依賴(set注入)
    其中全闷,構(gòu)造器的循環(huán)依賴問題無法解決,只能拋出BeanCurrentlyInCreationException異常萍启,在解決屬性循環(huán)依賴時总珠,Spring采用的是提前暴露對象的方法。

構(gòu)造器注入代碼如下

@Component
public class CircularDependencyA {

    private CircularDependencyB circB;

    @Autowired
    public CircularDependencyA(CircularDependencyB circB) {
        this.circB = circB;
    }
}
@Component
public class CircularDependencyB {

    private CircularDependencyA circA;

    @Autowired
    public CircularDependencyB(CircularDependencyA circA) {
        this.circA = circA;
    }
}

屬性注入代碼如下

@Component
public class CircularDependencyA {

    private CircularDependencyB circB;

    @Autowired
    public void setCircB(CircularDependencyB circB) {
        this.circB = circB;
    }

    public CircularDependencyB getCircB() {
        return circB;
    }
}

2. 循環(huán)依賴處理機制

  • 單例bean構(gòu)造器參數(shù)循環(huán)注入(無法解決)
  • prototype原型bean循環(huán)依賴(無法解決)
    對于原型bean的初始化無論是通過構(gòu)造器還是setXxx方法產(chǎn)生循環(huán)依賴時勘纯,Spring都會直接報錯處理局服。
    AbstractBeanFactory.doGetBean()?法:
            if (isPrototypeCurrentlyInCreation(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }
    protected boolean isPrototypeCurrentlyInCreation(String beanName) {
        Object curVal = this.prototypesCurrentlyInCreation.get();
        return (curVal != null &&
                (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
    }

在獲取bean之前如果這個原型bean正在被創(chuàng)建則直接拋出異常。原型bean在創(chuàng)建之前會進行標記這個beanName正在被創(chuàng)建驳遵,等創(chuàng)建結(jié)束之后會刪除標記淫奔。
AbstractBeanFactory#doGetBean

try {
 //創(chuàng)建原型bean之前添加標記
 beforePrototypeCreation(beanName);
 //創(chuàng)建原型bean
 prototypeInstance = createBean(beanName, mbd, args);
}
finally {
 //創(chuàng)建原型bean之后刪除標記
 afterPrototypeCreation(beanName);
}

總結(jié):Spring不支持原型bean的循環(huán)依賴。(舉個例子堤结,TestA唆迁,TestB兩個原型Bean相互依賴,當TestA被實例化竞穷,在屬性賦值的時候發(fā)現(xiàn)對象TestB需要實例化唐责,所以就去實例化TestB,到了TestB屬性賦值的時候又發(fā)現(xiàn)依賴TestA就開始創(chuàng)建TestA瘾带,然而TestA被標記正在創(chuàng)建中鼠哥,然后就被isPrototypeCurrentlyInCreation這個判斷攔截住了拋出異常)

  • 單例Bean通過setXxx或者@Autowired進行循環(huán)依賴

Spring的循環(huán)依賴的利率一句基于Java的引用傳遞,當獲得對象引用時看政,對象的屬性是可以延后設(shè)置的肴盏,但是構(gòu)造器必須在獲取引用之前。
Spring通過setXxx或者@Autowired方法解決循環(huán)依賴其實是通過提前暴露一個ObjectFactory對象來完成的帽衙,簡單來說ClassA在調(diào)用構(gòu)造器完成對象初始化之后菜皂,在調(diào)用ClassA的setClassB方法之前就把ClassA實例化的對象通過ObjectFactory提前暴露給Spring容器中。

  1. Spring容器初始化ClassA通過構(gòu)造器初始化對象提前暴露給Spring容器厉萝。
    AbstractAutowireCapableBeanFactory#doCreateBean
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            if (logger.isTraceEnabled()) {
                logger.trace("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
//將初始化后的對象提前已ObjectFactory對象注入到容器中
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }
  1. ClassA調(diào)用setClassB方法恍飘,Spring首先從容器中獲取ClassB榨崩,此時ClassB不存在Spring容器中
  2. Spring容器初始化ClassB,同時也將ClassB提前暴露給Spring容器
  3. ClassB調(diào)用setClassA方法章母,Spring從容器中獲取ClassA母蛛,因為第一步已經(jīng)提前暴露了ClassA,因此可以獲取到ClassA實例乳怎。
    ClassA通過spring容器獲取到ClassB彩郊,完成對象初始化操作
    5.這樣ClassA和ClassB都完成了對象初始化操作,解決了循環(huán)依賴的問題蚪缀。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秫逝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子询枚,更是在濱河造成了極大的恐慌违帆,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件金蜀,死亡現(xiàn)場離奇詭異刷后,居然都是意外死亡,警方通過查閱死者的電腦和手機渊抄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門尝胆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人护桦,你說我怎么就攤上這事班巩。” “怎么了嘶炭?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵抱慌,是天一觀的道長。 經(jīng)常有香客問我眨猎,道長抑进,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任睡陪,我火速辦了婚禮寺渗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘兰迫。我一直安慰自己信殊,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布汁果。 她就那樣靜靜地躺著涡拘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪据德。 梳的紋絲不亂的頭發(fā)上鳄乏,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天跷车,我揣著相機與錄音,去河邊找鬼橱野。 笑死朽缴,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的水援。 我是一名探鬼主播密强,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蜗元!你這毒婦竟也來了或渤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤许帐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后毕谴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體成畦,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年涝开,在試婚紗的時候發(fā)現(xiàn)自己被綠了循帐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡舀武,死狀恐怖拄养,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情银舱,我是刑警寧澤瘪匿,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站寻馏,受9級特大地震影響棋弥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诚欠,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一顽染、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧轰绵,春花似錦粉寞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至液样,卻和暖如春业崖,著一層夾襖步出監(jiān)牢的瞬間野芒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工双炕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留狞悲,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓妇斤,卻偏偏與公主長得像摇锋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子站超,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

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