當(dāng)Spring循環(huán)依賴(lài)遇上了BeanPostProcessor動(dòng)態(tài)代理

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

假設(shè)Spring容器中有兩個(gè)Bean:A和B

依賴(lài)關(guān)系如下:

A->B->A

@Component
public class CircularA {

    @Autowired
    private CircularB b;

    public CircularA() {
    }

    public void setB(CircularB b) {
        this.b = b;
    }
}
@Component
public class CircularB {

    @Autowired
    private CircularA a;

    public CircularB() {
    }

    public void setA(CircularA a) {
        this.a = a;
    }
}

Spring容器在創(chuàng)建BeanA的時(shí)候,發(fā)現(xiàn)需要依賴(lài)BeanB桥状,那么在創(chuàng)建BeanB的時(shí)候冒窍,發(fā)現(xiàn)需要依賴(lài)BeanA俯树,如此就形成循環(huán)依賴(lài)闰歪。

2. Spring怎么解決循環(huán)依賴(lài)

在網(wǎng)上有很多相關(guān)的博客解釋Spring如何解決循環(huán)依賴(lài)Spring解決循環(huán)依賴(lài)

簡(jiǎn)而言之就是Spring通過(guò)三級(jí)緩存來(lái)解決循環(huán)依賴(lài)蝌蹂。在Spring容器初始化過(guò)程中噩斟,通過(guò)beanName獲取Bean的優(yōu)先級(jí)依次是:一級(jí)緩存->二級(jí)緩存->三級(jí)緩存

    /** Cache of singleton objects: bean name --> bean instance */
    /** 一級(jí)緩存 */
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

    /** Cache of singleton factories: bean name --> ObjectFactory */
    /** 三級(jí)緩存 */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

    /** Cache of early singleton objects: bean name --> bean instance */
    /** 三級(jí)緩存 */
    private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

當(dāng)BeanFactory實(shí)例化BeanA后,BeanFactory會(huì)把剛剛實(shí)例化還沒(méi)有依賴(lài)注入的Bean包裝成一個(gè)ObjectFactory對(duì)象放入到三級(jí)緩存中孤个,并且從二級(jí)緩存中移除剃允,代碼如下

    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);
            }
        }
    }

接下來(lái)進(jìn)行依賴(lài)注入,如果存在循環(huán)依賴(lài)齐鲤,例如A->B->A的情況硅急,A實(shí)例化完畢,注入A.b的時(shí)候佳遂,要實(shí)例化B,發(fā)現(xiàn)依賴(lài)a撒顿,這個(gè)時(shí)候就要從BeanFactory中獲取a實(shí)例丑罪,這個(gè)時(shí)候,緩存升級(jí)了。下面方法的第二個(gè)參數(shù)是true

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();
            /** 將獲取到的Bean從三級(jí)緩存中移除吩屹,并且升級(jí)到二級(jí)緩存中 */
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }

b實(shí)例自己先完成實(shí)例化和依賴(lài)注入(這個(gè)時(shí)候a實(shí)例只是剛剛實(shí)例化跪另,但是已經(jīng)可以滿足beanB的需求了)以及初始化等聲明周期,最后在返回到a的創(chuàng)建流程中煤搜,a實(shí)例就可以注入已經(jīng)成熟的b實(shí)例,a實(shí)例自身也順利完成創(chuàng)建免绿,由于b實(shí)例持有了a實(shí)例的引用,所以在后續(xù)的使用中是完全沒(méi)有問(wèn)題的擦盾。

如果Spring中不存在Bean的循環(huán)依賴(lài)嘲驾,應(yīng)該是不存在從三級(jí)緩存升級(jí)到二級(jí)緩存的場(chǎng)景,因?yàn)镾pring是單線程初始化的迹卢。

這樣Spring解決Bean循環(huán)依賴(lài)的問(wèn)題A晒省!腐碱!

3. BeanPostProcessor

BeanPostProcessor接口是用來(lái)對(duì)bean進(jìn)行后置處理的誊垢,這個(gè)時(shí)候bean已經(jīng)完成實(shí)例化和依賴(lài)注入了,屬于bean初始化生命周期的一部分症见。

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
        if (System.getSecurityManager() != null) {
            AccessController.doPrivileged(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    invokeAwareMethods(beanName, bean);
                    return null;
                }
            }, getAccessControlContext());
        }
        else {
            invokeAwareMethods(beanName, bean);
        }

        Object wrappedBean = bean;
        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        }

        try {
            invokeInitMethods(beanName, wrappedBean, mbd);
        }
        catch (Throwable ex) {
            throw new BeanCreationException(
                    (mbd != null ? mbd.getResourceDescription() : null),
                    beanName, "Invocation of init method failed", ex);
        }

        if (mbd == null || !mbd.isSynthetic()) {
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }
        return wrappedBean;
    }

如果在BeanPostProcessor的接口中喂走,對(duì)傳入的bean進(jìn)行了處理導(dǎo)致返回的bean和傳入的bean不是同一個(gè)bean,這個(gè)正常情況是沒(méi)有問(wèn)題谋作,很多中間件都是這么做的

但是S蟪Α!瓷们!當(dāng)Spring 循環(huán)依賴(lài)遇上BeanProcessor返回一個(gè)不一致對(duì)象的時(shí)候业栅,就會(huì)發(fā)生問(wèn)題了!C巍碘裕!

4. 異常情況

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'xxx': Bean with name 'xxx' has been injected into other beans [a,b,c] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

問(wèn)題的描述就是這個(gè)樣子了,大致的意思就是xxx這個(gè)bean已經(jīng)注入到很多bean中了攒钳,只不過(guò)呢依賴(lài)xxx的bean中引用的不是它的最終版本帮孔,因?yàn)樗麄冎g存在循環(huán)依賴(lài)的問(wèn)題,在解決循環(huán)依賴(lài)中使用的是二級(jí)緩存中的early bean不撑,而解決完循環(huán)依賴(lài)后文兢,bean的引用發(fā)生了變化,導(dǎo)致了early bean和 expose bean不相等焕檬,所以拋出異常了D芳帷!实愚!

Object earlySingletonReference = getSingleton(beanName, false);
            if (earlySingletonReference != null) {
                if (exposedObject == bean) {
                    exposedObject = earlySingletonReference;
                }
                else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                    String[] dependentBeans = getDependentBeans(beanName);
                    Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
                    for (String dependentBean : dependentBeans) {
                        if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                            actualDependentBeans.add(dependentBean);
                        }
                    }
                    if (!actualDependentBeans.isEmpty()) {
                        throw new BeanCurrentlyInCreationException(beanName,
                                "Bean with name '" + beanName + "' has been injected into other beans [" +
                                StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                                "] in its raw version as part of a circular reference, but has eventually been " +
                                "wrapped. This means that said other beans do not use the final version of the " +
                                "bean. This is often the result of over-eager type matching - consider using " +
                                "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
                    }
                }
            }

5. 解決方案

找到問(wèn)題原因了兼呵,那么問(wèn)題的解決通常就有了

  1. 項(xiàng)目中盡量避免Spring的循環(huán)引用兔辅,這本來(lái)就是不合理的。

  2. 使用@Lazy加載機(jī)制來(lái)解決

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末击喂,一起剝皮案震驚了整個(gè)濱河市维苔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌懂昂,老刑警劉巖介时,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異凌彬,居然都是意外死亡沸柔,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)饿序,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)勉失,“玉大人,你說(shuō)我怎么就攤上這事原探÷以洌” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵咽弦,是天一觀的道長(zhǎng)徒蟆。 經(jīng)常有香客問(wèn)我,道長(zhǎng)型型,這世上最難降的妖魔是什么段审? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮闹蒜,結(jié)果婚禮上寺枉,老公的妹妹穿的比我還像新娘。我一直安慰自己绷落,他們只是感情好姥闪,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著砌烁,像睡著了一般筐喳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上函喉,一...
    開(kāi)封第一講書(shū)人閱讀 52,196評(píng)論 1 308
  • 那天避归,我揣著相機(jī)與錄音,去河邊找鬼管呵。 笑死梳毙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的捐下。 我是一名探鬼主播顿天,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼堂氯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了牌废?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤啤握,失蹤者是張志新(化名)和其女友劉穎鸟缕,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體排抬,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡懂从,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蹲蒲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片番甩。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖届搁,靈堂內(nèi)的尸體忽然破棺而出缘薛,到底是詐尸還是另有隱情,我是刑警寧澤卡睦,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布宴胧,位于F島的核電站,受9級(jí)特大地震影響表锻,放射性物質(zhì)發(fā)生泄漏恕齐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一瞬逊、第九天 我趴在偏房一處隱蔽的房頂上張望显歧。 院中可真熱鬧,春花似錦确镊、人聲如沸士骤。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)敦间。三九已至,卻和暖如春束铭,著一層夾襖步出監(jiān)牢的瞬間廓块,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工契沫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留带猴,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓懈万,卻偏偏與公主長(zhǎng)得像拴清,于是被迫代替她去往敵國(guó)和親靶病。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理口予,服務(wù)發(fā)現(xiàn)娄周,斷路器,智...
    卡卡羅2017閱讀 134,695評(píng)論 18 139
  • 1.1 Spring IoC容器和bean簡(jiǎn)介 本章介紹了Spring Framework實(shí)現(xiàn)的控制反轉(zhuǎn)(IoC)...
    起名真是難閱讀 2,587評(píng)論 0 8
  • 1.1 spring IoC容器和beans的簡(jiǎn)介 Spring 框架的最核心基礎(chǔ)的功能是IoC(控制反轉(zhuǎn))容器沪停,...
    simoscode閱讀 6,721評(píng)論 2 22
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,844評(píng)論 6 342
  • 看到朋友圈鋪天蓋地的關(guān)于高考的訊息煤辨,和來(lái)自四面八方對(duì)考生的祝福,讓我不禁又想到了我高考那年木张,不禁又想到那些高中生活...
    不負(fù)桃李閱讀 186評(píng)論 0 1