循環(huán)依賴

1.單例Bean循環(huán)依賴-setter產(chǎn)生的循環(huán)依賴

1.1 原理

為單例搞的三個 map:

  • 一級緩存,singletonObjects烂琴,存儲所有已創(chuàng)建完畢的單例 Bean (完整的 Bean)
  • 二級緩存,earlySingletonObjects,存儲所有僅完成實例化蓝晒,但還未進行屬性注入和初始化的 Bean
  • 三級緩存形用,singletonFactories,存儲能建立這個 Bean 的一個工廠,通過工廠能獲取這個 Bean妓雾,延遲化 Bean 的生成,工廠生成的 Bean 會塞入二級緩存

getBean()步驟:

  • getSingleton(beanName) 依次從三個緩存中獲取
  • 實例化
  • addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  • 屬性注入
  • 初始化

一個大致過程: getBean(A)肩狂,AB循環(huán)依賴

  • A執(zhí)行屬性注入郊霎,這個時候 A 發(fā)現(xiàn)需要注入 B,所以去 getBean(B)移袍,此時又會走一遍上面描述的邏輯解藻。
  • 到了 B 的屬性注入這一步,此時 B 調(diào)用 getBean(A)葡盗,這時候一級緩存里面找不到舆逃,但是發(fā)現(xiàn) A 正在創(chuàng)建中的,于是去二級緩存找,發(fā)現(xiàn)沒找到路狮,于是去三級緩存找虫啥,然后找到了。
    并且通過上面提前在三級緩存里暴露的工廠得到 A奄妨,然后將這個工廠從三級緩存里刪除涂籽,并將 A 加入到二級緩存中。
    然后結(jié)果就是 B 屬性注入成功砸抛。
    緊接著 B 調(diào)用 initializeBean 初始化评雌,最終返回,此時 B 已經(jīng)被加到了一級緩存里 直焙。
  • 這時候就回到了 A 的屬性注入景东,此時注入了 B,接著執(zhí)行初始化奔誓,最后 A 也會被加到一級緩存里斤吐,且從二級緩存中刪除 A。

1.2 getSingleton()

getSingleton()并發(fā)安全問題厨喂?以及5.3后變了和措,為啥變了?

    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // Quick check for existing instance without full singleton lock
        // 到一級緩存獲取
        Object singletonObject = this.singletonObjects.get(beanName);
        // 條件一:一級緩存不存在
        //   1)單實例未創(chuàng)建
        //   2)單實例循環(huán)依賴(三級緩存解決setter循環(huán)依賴)

        //1.假設Spring先實例化A蜕煌,首先拿到A的構(gòu)造方法派阱,進行反射創(chuàng)建出來A的早期實例對象,這個時候斜纪,這個早期對象被包裝成了ObjectFactory對象贫母,放到了3級緩存。
        //2.處理A的依賴數(shù)據(jù)盒刚,檢查發(fā)現(xiàn)颁独,A它依賴了B對象,所以接下來伪冰,Spring就會去根據(jù)B類型到容器中去getBean(B.class)誓酒,這里就遞歸了。
        //3.拿到B的構(gòu)造方法贮聂,進行反射創(chuàng)建出來B的早期實例對象靠柑,它也會把B包裝成ObjectFactory對象,放到3級緩存吓懈。
        //4.處理B的依賴數(shù)據(jù)歼冰,檢查發(fā)現(xiàn),B它依賴了A對象耻警,所以接下來隔嫡,Spring就會去根據(jù)A類型到容器中去getBean(A.class)甸怕,去拿A對象,這個又遞歸了腮恩。
        //5.程序還會走到當前這個方法梢杭。getSingleton這個方法。
        //6.條件一成立秸滴,條件二也會成立武契。
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            // 檢查二級緩存
            singletonObject = this.earlySingletonObjects.get(beanName);
            // 二級緩存沒有
            if (singletonObject == null && allowEarlyReference) {
                synchronized (this.singletonObjects) {
                    // Consistent creation of early reference within full singleton lock
                    singletonObject = this.singletonObjects.get(beanName);
                    if (singletonObject == null) {
                        singletonObject = this.earlySingletonObjects.get(beanName);
                        if (singletonObject == null) {
                            //Spring為什么需要有3級緩存存在,而不是只有2級緩存呢荡含?
                            //AOP咒唆,靠什么實現(xiàn)的呢?動態(tài)代理
                            //靜態(tài)代理:需要手動寫代碼释液,實現(xiàn)一個新的java文件全释,這個java類 和 需要代理的對象 實現(xiàn)同一個接口,內(nèi)部維護一個被代理對象(原生)
                            //代理類误债,在調(diào)用原生對象前后浸船,可以加一些邏輯. 代理對象 和 被代理對象 是兩個不同的對象,內(nèi)存地址一定是不一樣的找前。
                            //動態(tài)代理:不需要人為寫代碼了,而是依靠字節(jié)碼框架動態(tài)生成class字節(jié)碼文件判族,然后jvm再加載躺盛,然后也一樣 也是去new代理對象,這個
                            //代理對象 沒啥特殊的形帮,也是內(nèi)部保留了 原生對象槽惫,然后在調(diào)用原生對象前后 實現(xiàn)的 字節(jié)碼增強。
                            //3級緩存在這里有什么目的呢辩撑?
                            //3級緩存里面保存的是對象工廠界斜,這個對象工廠內(nèi)部保留著最原生的對象引用,ObjectFactory的實現(xiàn)類合冀,getObject()方法各薇,它需要考慮一個問題。
                            //它到底要返回原生的君躺,還是增強后的峭判。
                            //getObject會判斷當前這個早期實例 是否需要被增強,如果是棕叫,那么提前完成動態(tài)代理增強林螃,返回代理對象。否則俺泣,返回原生對象疗认。
                            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                            if (singletonFactory != null) {
                                // 將三級緩存數(shù)據(jù)提升到二級
                                singletonObject = singletonFactory.getObject();
                                this.earlySingletonObjects.put(beanName, singletonObject);
                                this.singletonFactories.remove(beanName);
                            }
                        }
                    }
                }
            }
        }
        return singletonObject;
    }

1.3 為啥需要三級緩存完残?

三級緩存是否為延遲代理的創(chuàng)建,盡量不打破 Bean 的生命周期

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

如果有代理的話横漏,那么我們想要直接拿到的是代理對象谨设。
也就是說如果 A 需要被代理,那么 B 依賴的 A 是已經(jīng)被代理的 A绊茧,所以我們不能返回 A 給 B铝宵,而是返回代理的 A 給 B。
這個工廠的作用就是判斷這個對象是否需要代理华畏,如果否則直接返回鹏秋,如果是則返回代理對象。

正常代理對象的生成是基于后置處理器亡笑,是 在被代理的對象初始化后期調(diào)用生成的 侣夷, 所以如果你提早代理了其實是違背了 Bean 定義的生命周期 。
所以 Spring 先在一個三級緩存放置一個工廠仑乌,如果產(chǎn)生循環(huán)依賴百拓,那么就調(diào)用這個工廠提早得到代理對象。
如果沒產(chǎn)生依賴晰甚,這個工廠根本不會被調(diào)用衙传,所以 Bean 的生命周期就是對的。

其實破壞循環(huán)依賴厕九,其實只有二級緩存就夠了蓖捶,但是礙于生命周期的問題,提前暴露工廠延遲代理對象的生成扁远。

2.單例Bean循環(huán)依賴-構(gòu)造參數(shù)產(chǎn)生的循環(huán)依賴

2.1 構(gòu)造參數(shù)為啥不能解決循環(huán)依賴

如果全是構(gòu)造器注入俊鱼,比如 A(B b) ,那表明在 new 的時候畅买,就需要得到 B并闲,此時需要 new B 。
但是 B 也是要在構(gòu)造的時候注入 A 谷羞,即 B(A a) 帝火,這時候 B 需要在一個 map 中找到不完整的 A ,發(fā)現(xiàn)找不到湃缎。
為什么找不到购公?因為 A 還沒 new 完呢,所以找到不完整的 A雁歌, 因此如果全是構(gòu)造器注入的話宏浩,那么 Spring 無法處理循環(huán)依賴 。

總結(jié):

  • 如果循環(huán)依賴都是構(gòu)造器注入靠瞎,則失敗
  • 如果循環(huán)依賴不完全是構(gòu)造器注入比庄,則可能成功求妹,可能失敗,具體跟BeanName的字母序有關(guān)系佳窑。

2.2 源碼中的處理

getBean()步驟:

  • getSingleton(beanName) 依次從三個緩存中獲取
  • getSingleton(beanName, ObjectFactory)
  • beforeSingletonCreation(beanName) 處理構(gòu)造器循環(huán)依賴
  • 實例化制恍,這里會getBean()構(gòu)造器參數(shù)
  • addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  • 屬性注入
  • 初始化
    public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(beanName, "Bean name must not be null");
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {
                //容器銷毀時,會設置這個屬性為true神凑,這個時候就不能再創(chuàng)建bean實例了净神,直接拋錯。
                if (this.singletonsCurrentlyInDestruction) {
                    throw new BeanCreationNotAllowedException(beanName,
                            "Singleton bean creation not allowed while singletons of this factory are in destruction " +
                            "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
                }
                //將當前beanName放入到“正在創(chuàng)建中單實例集合”溉委,放入成功鹃唯,說明沒有產(chǎn)生循環(huán)依賴,失敗瓣喊,則產(chǎn)生循環(huán)依賴坡慌,里面會拋異常。
                //舉個例子:構(gòu)造方法參數(shù)依賴
                //A->B    B->A
                //1.加載A藻三,根據(jù)A的構(gòu)造方法洪橘,想要去實例化A對象,但是發(fā)現(xiàn)A的構(gòu)造方法有一個參數(shù)是B(在這之前棵帽,已經(jīng)向這個集合中添加了 {A})
                //2.因為A的構(gòu)造方法依賴B熄求,所以觸發(fā)了加載B的邏輯..
                //3.加載B,根據(jù)B的構(gòu)造方法逗概,想要去實例化B對象弟晚,但是發(fā)現(xiàn)B的構(gòu)造方法有一個參數(shù)是A(在這之前,已經(jīng)向這個集合中添加了 {A仗谆,B})
                //4.因為B的構(gòu)造方法依賴A指巡,所以再次觸發(fā)了加載A的邏輯..
                //5.再次來到這個getSingleton方法里淑履,調(diào)用beforeSingletonCreation(A),因為創(chuàng)建中集合 已經(jīng)有A了隶垮,所以添加失敗,拋出異常
                //完事秘噪。
                beforeSingletonCreation(beanName);
    protected void beforeSingletonCreation(String beanName) {
        if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
    }

3.原型模式的循環(huán)依賴

3.1 原型模式為啥不能解決循環(huán)依賴

如果兩個 Bean 都是原型模式的話狸吞。

那么創(chuàng)建 A1 需要創(chuàng)建一個 B1。
創(chuàng)建 B1 的時候要創(chuàng)建一個 A2指煎。
創(chuàng)建 A2 又要創(chuàng)建一個 B2蹋偏。
創(chuàng)建 B2 又要創(chuàng)建一個 A3。
創(chuàng)建 A3 又要創(chuàng)建一個 B3.....
因為原型模式都需要創(chuàng)建新的對象至壤,不能用以前的對象威始。

3.2 源碼中的處理

getBean()步驟:

  • getSingleton(beanName) 依次從三個緩存中獲取
  • 原型循環(huán)依賴問題判定
  • beforePrototypeCreation(beanName); 加入到prototypesCurrentlyInCreation
  • 實例化
  • 屬性注入
  • 初始化
  • afterPrototypeCreation(beanName);
            //1、原型循環(huán)依賴問題判定
            //舉個例子:
            //prototypeA -> B像街, B -> prototypeA
            //1.會向正在創(chuàng)建中的原型集合內(nèi)添加一個字符串 “A”   下面beforePrototypeCreation(beanName);
            //2.創(chuàng)建prototypeA對象黎棠,只是一個早期對象晋渺。
            //3.處理prototypeA的依賴,發(fā)現(xiàn)A依賴了B類型的對象
            //4.觸發(fā)了Spring.getBean(“B”)的操作脓斩。
            //5.根據(jù)B的構(gòu)造方法反射創(chuàng)建出來了B的早期實例
            //6.Spring處理B對象的依賴木西,發(fā)現(xiàn)依賴了A。
            //7.Spring轉(zhuǎn)頭回來再次去獲取A去了随静。getBean(“A”).
            //8.條件會返回true八千,最終拋出異常,算是結(jié)束了循環(huán)依賴注入燎猛。
            if (isPrototypeCurrentlyInCreation(beanName)) {
                throw new BeanCurrentlyInCreationException(beanName);
            }

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恋捆,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子扛门,更是在濱河造成了極大的恐慌鸠信,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件论寨,死亡現(xiàn)場離奇詭異星立,居然都是意外死亡,警方通過查閱死者的電腦和手機葬凳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門绰垂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人火焰,你說我怎么就攤上這事劲装。” “怎么了昌简?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵占业,是天一觀的道長。 經(jīng)常有香客問我纯赎,道長谦疾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任犬金,我火速辦了婚禮念恍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘晚顷。我一直安慰自己峰伙,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布该默。 她就那樣靜靜地躺著瞳氓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栓袖。 梳的紋絲不亂的頭發(fā)上匣摘,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天锅锨,我揣著相機與錄音,去河邊找鬼恋沃。 笑死必搞,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的囊咏。 我是一名探鬼主播恕洲,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼梅割!你這毒婦竟也來了霜第?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤户辞,失蹤者是張志新(化名)和其女友劉穎泌类,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體底燎,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡刃榨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了双仍。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片枢希。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖朱沃,靈堂內(nèi)的尸體忽然破棺而出苞轿,到底是詐尸還是另有隱情,我是刑警寧澤逗物,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布搬卒,位于F島的核電站,受9級特大地震影響翎卓,放射性物質(zhì)發(fā)生泄漏契邀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一莲祸、第九天 我趴在偏房一處隱蔽的房頂上張望蹂安。 院中可真熱鬧椭迎,春花似錦锐帜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至简软,卻和暖如春蛮拔,著一層夾襖步出監(jiān)牢的瞬間述暂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工建炫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留畦韭,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓肛跌,卻偏偏與公主長得像艺配,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子衍慎,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

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