循環(huán)依賴的底層原理

從 Spring IOC 容器中獲取 bean 實例的流程:從context.getBean()方法開始


發(fā)生了循環(huán)依賴:

public class A {
    @Autowired
    private B b;
}
public class B {
  @Autowired
    private A a;
}

獲取單例

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//從一級緩存(單例池)中查找對象
        Object singletonObject = this.singletonObjects.get(beanName);
//單例池中找不到半沽,而且被查找的bean正在創(chuàng)建中--發(fā)生了循環(huán)依賴
//通過singletonsCurrentlyInCreation(set)記錄對象是否正在被創(chuàng)建
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
//從二級緩存中去尋找 原始對象或者是代理對象
                singletonObject = this.earlySingletonObjects.get(beanName);
//二級緩存中找不到 就去三級緩存中找
                if (singletonObject == null && allowEarlyReference) {
//這里取得三級緩存中對應的lambda表達式的值--得到原始對象(發(fā)生循環(huán)依賴但未aop)或者是代理對象(發(fā)生循環(huán)依賴和aop--提前aop)
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
//執(zhí)行上一步獲取的lambda表達式(getEarlyBeanReference方法)
//--有aop,就提前執(zhí)行AOP--得到一個代理對象
//-- 無aop腕唧,就得到一個原始對象
                        singletonObject = singletonFactory.getObject();// 提前曝光 bean 實例(raw bean)翎朱,用于解決循環(huán)依賴
//將通過三級緩存得到的結果 放入二級緩存
                        this.earlySingletonObjects.put(beanName, singletonObject);
//移除三級緩存中對應的內容
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }

三級緩存為什么會移除掉?
在三級緩存中找 找到了就會執(zhí)行AOp 產出代理對象 然后將代理對象放入到二級緩存忌堂。三級緩存一定會能到的對象 但不一定 會執(zhí)行 aop 二級緩存找不到 才會觸發(fā)aop(通過lambda表達式盒至,執(zhí)行函數式接口,執(zhí)行aop) 產生代理對象放入二級緩存士修,放入之后需要移除掉對應的三級緩存(保證只執(zhí)行一次aop)如果三級緩存中對象不需要執(zhí)行aop操作 枷遂,那么產生的對象仍然要放入二級緩存 ,這是放入的對象是原始對象

為什么單例池:concurentHashmap是李命,二級緩存是hashmap登淘,三級緩存是hashmap?
三級緩存的AOP過程需要加鎖以保證操作的原子性
因為三級緩存的函數接口 內部已經加了鎖封字,保證了操作的原子性 所以沒必要使用concurenthashmap

問題:源碼中加synchronized鎖的意義?

背景:
二級緩存中的aService對象是三級緩存中的lambda表達式生成出來的耍鬓,
他們是成對的阔籽,二級緩存中存在代理對象,則三級緩存中不應該存在lambda表達式牲蜀;
或者說笆制,三級緩存中存在lambda表達式,則二級緩存中不應當有該代理對象

解答:

  • 3級緩存中的 value是一個lambda表達式涣达,一執(zhí)行就是進行AOP在辆,得到代理對象,所以lambda表達式應當只執(zhí)行一次度苔,且執(zhí)行完畢后從3級緩存中進行移除匆篓,以防止其他的代碼又拿出來執(zhí)行了
  • 在第2、第3級緩存只能有一個地方存在操作對象寇窑,要么是lambda表達式(三級緩存)鸦概,要么是lambda執(zhí)行后的代理對象(二級緩存)。這是原子性的甩骏,為了對高并發(fā)情況進行控制窗市,加鎖進行同步先慷。
  • 1級緩存定義為 concurrentHashMap。 2級咨察、3級緩存定義為簡單的HashMap论熙,是因為 2、3級緩存是成對出現的摄狱,哪怕是定義成concurrentHashMap脓诡,也要加鎖保持兩個Map的操作的原子性

創(chuàng)建Bean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {

        // Instantiate the bean.
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
//得到原始對象 -- 屬性未賦值
        final Object bean = instanceWrapper.getWrappedInstance();
        Class<?> beanType = instanceWrapper.getWrappedClass();
        if (beanType != NullBean.class) {
            mbd.resolvedTargetType = beanType;
        }

        // Allow post-processors to modify the merged bean definition.
        synchronized (mbd.postProcessingLock) {
            if (!mbd.postProcessed) {
                try {
                    applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
                }
                catch (Throwable ex) {
                    throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                            "Post-processing of merged bean definition failed", ex);
                }
                mbd.postProcessed = true;
            }
        }

        // Eagerly cache singletons to be able to resolve circular references
        // even when triggered by lifecycle interfaces like BeanFactoryAware.
//如果當前創(chuàng)建的是單例bean,并且允許著環(huán)依賴,并且還在創(chuàng)建過程中二蓝,那么則提早暴露--一般均為true 
//創(chuàng)建就暴露--一般單例都會暴露出去--都會存入三級緩存
        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");
            }
//針對發(fā)生了循環(huán)依賴的情況
//lambda執(zhí)行的結果存入三級緩存中
//getEarlyBeanReference--wrapIfNecessary方法--判斷對象創(chuàng)建過程中是否存在AOP
//--需要誉券,就提前aop,存入代理對象,不需要就存入原始對象
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }

        // Initialize the bean instance.
//暴露原始對象
        Object exposedObject = bean;
        try {
//屬性填充@Autowired --B對象填充原始對象 
            populateBean(beanName, mbd, instanceWrapper);
//正常情況下進行AOP的地方--postProcessAfterInitialization方法
//  --判斷是否提前進行了AOP(使用Map:earlyProxyReferences記錄提前 進行的aop)刊愚,如果沒有提前aop踊跟,才會在這里執(zhí)行aop
//初始化Bean--可能包含AOP--如果需要AOP,則需要使用原始對象(針對非循環(huán)依賴的時候)
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        catch (Throwable ex) {
            if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
                throw (BeanCreationException) ex;
            }
            else {
                throw new BeanCreationException(
                        mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
            }
        }

    ····················
        // Register bean as disposable.
        try {
            registerDisposableBeanIfNecessary(beanName, bean, mbd);
        }
        catch (BeanDefinitionValidationException ex) {
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
        }

        return exposedObject;
    }

其中:

 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

是將原始對象信息存入三級緩存的操作,存入的是lambda表達式:
getEarlyBeanReference(beanName, mbd, bean)執(zhí)行的結果,getEarlyBeanReference會對是否需要提前AOP進行判斷鸥诽,如果需要進行AOP商玫,則生成代理對象放入二級緩存。

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

其中:

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

        Object wrappedBean = bean;
        if (mbd == null || !mbd.isSynthetic()) {
//對類中某些特殊方法的調用牡借,比如 @PostConstruct拳昌,Aware接口
            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
        }

        try {
//InitializingBean接口,afterPropertiesSet钠龙,init-method屬性調用
            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()) {
//AOP入口
            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
        }

        return wrappedBean;
    }

整個流程:

1炬藤、A doCreateBean()初始化,由于還未創(chuàng)建碴里,從一級緩存查不到沈矿,此時只是一個半成品(提前暴露的對象),放入三級緩存singletonFactories;
2咬腋、A發(fā)現自己需要B對象羹膳,但是三級緩存中未發(fā)現B,創(chuàng)建B的原始對象根竿,將帶有B對象信息(beanName,bd,原始對象)的Lambda表達式放入singletonFactories;
3陵像、B發(fā)現自己需要A對象,從一級緩存singletonObjects沒找到寇壳,并知道了A對象正在創(chuàng)建醒颖,就確認發(fā)生了循環(huán)依賴,這時候再去二級緩存earlySingletonObjects中尋找A對象九巡,沒找到就繼續(xù)在三級緩存singletonFactories中尋找A對象(一定能找到)图贸,于是執(zhí)行三級緩存中的lambda表達式得到一個代理對象或者是原始對象A(A中屬性未賦值),將A放入二級緩存earlySingletonObjects,同時從三級緩存刪除對應beanName的表達式疏日;
同理向三級緩存加入對象時偿洁,也會從二級緩存中將相同BeanName的記錄刪除掉,所以二級緩存與三級緩存的之間的來兩步操作具有原子性沟优。

this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);

4涕滋、將A注入到對象B中;
5挠阁、B完成屬性填充宾肺,執(zhí)行初始化方法,將自己放入第一級緩存中(此時B是一個完整的對象)侵俗;
6锨用、A得到對象B,將B注入到A中隘谣;
7增拥、A完成屬性填充,初始化寻歧,并放入到一級緩存中

注意:在對象創(chuàng)建開始的時候掌栅,會對對象創(chuàng)建狀態(tài)利用Set:
singletonsCurrentlyInCreation進行記錄:是否是正在創(chuàng)建,可用于判斷是否發(fā)生了循環(huán)依賴。

@Lazy注解的作用:

initializeBean
Spring解決循環(huán)依賴問題--B站視頻講解
Spring 循環(huán)依賴的“常見”面試問題

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末码泛,一起剝皮案震驚了整個濱河市猾封,隨后出現的幾起案子,更是在濱河造成了極大的恐慌噪珊,老刑警劉巖晌缘,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異痢站,居然都是意外死亡枚钓,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門瑟押,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人星掰,你說我怎么就攤上這事多望。” “怎么了氢烘?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵怀偷,是天一觀的道長。 經常有香客問我播玖,道長椎工,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮维蒙,結果婚禮上掰吕,老公的妹妹穿的比我還像新娘。我一直安慰自己颅痊,他們只是感情好殖熟,可當我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著斑响,像睡著了一般菱属。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舰罚,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天纽门,我揣著相機與錄音,去河邊找鬼营罢。 笑死赏陵,一個胖子當著我的面吹牛,可吹牛的內容都是我干的愤钾。 我是一名探鬼主播瘟滨,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼能颁!你這毒婦竟也來了杂瘸?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤伙菊,失蹤者是張志新(化名)和其女友劉穎败玉,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體镜硕,經...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡运翼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了兴枯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片血淌。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖财剖,靈堂內的尸體忽然破棺而出悠夯,到底是詐尸還是另有隱情,我是刑警寧澤躺坟,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布沦补,位于F島的核電站,受9級特大地震影響咪橙,放射性物質發(fā)生泄漏夕膀。R本人自食惡果不足惜虚倒,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望产舞。 院中可真熱鬧魂奥,春花似錦、人聲如沸庞瘸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽擦囊。三九已至违霞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瞬场,已是汗流浹背买鸽。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贯被,地道東北人眼五。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像彤灶,于是被迫代替她去往敵國和親看幼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內容