spring循環(huán)依賴一定要三級緩存嗎胶逢?就算用來解決AOP厅瞎,也需要三級緩存嗎?

參考文章:Spring 為何需要三級緩存解決循環(huán)依賴宪塔,而不是二級緩存

Spring是如何利用"三級緩存"巧妙解決Bean的循環(huán)依賴問題

個(gè)人理解:
1、其實(shí)把getEarlyBeanReference生成的對象直接保存到二級緩存囊拜,無需三級緩存用ObjectFacotry封裝原始bean也可以解決循環(huán)依賴某筐。三級緩存感覺純粹是為了延遲調(diào)用aop邏輯而已。

2冠跷、其實(shí)把getEarlyBeanReference生成的對象直接暴露到一級緩存也是可以的南誊。只要引用的地址不變身诺,誰要用就提前給誰。初始化動(dòng)作可以后面慢慢做抄囚。只要引用不變霉赡,它初始化完成后,所有引用它的bean都自然而然的能得到完成的該bean幔托⊙鳎可能spring擔(dān)心一級緩存既用來存放單例bean,又用來存放提前暴露的bean重挑,會(huì)引起混亂嗓化。所以,上面徐庶老師說的谬哀,只要膽子大刺覆,一級緩存夠用。解決循環(huán)依賴的核心史煎,不在乎幾級緩存谦屑,而在于提前暴露引用地址即可。

一篇梭、先交代下什么是循環(huán)依賴氢橙,什么是三級緩存

循環(huán)依賴:A依賴B,B依賴A
三級緩存:

//一級緩存很洋,用來存放初始化完成的單例bean
Map<String, Object> singletonObjects;
//二級緩存充蓝,用來存放提前暴露的原始bean
Map<String, Object> earlySingletonObjects;
//三級緩存,用來存放 “包裝提前暴露的原始bean”的ObjectFactory對象
Map<String, ObjectFactory<?>> singletonFactories;

有人可能會(huì)問喉磁,提前暴露的對象已經(jīng)存放在二級緩存了谓苟,為啥還要在三級緩存中存放呢?下文會(huì)詳細(xì)解釋协怒。

二涝焙、循環(huán)依賴的解決

    protected <T> T doGetBean(
            final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
            throws BeansException {

        final String beanName = transformedBeanName(name);
        Object bean;

        // Eagerly check singleton cache for manually registered singletons.
        Object sharedInstance = getSingleton(beanName);
        .....//省略

getBean的時(shí)候,一上來就先去拿一下提前暴露的bean對象孕暇。

getSingleton方法如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //先去一級緩存拿仑撞。新創(chuàng)建的bean,這里一定拿不到
        Object singletonObject = this.singletonObjects.get(beanName);
        //拿不到初始化完成的bean妖滔,且該bean正在被創(chuàng)建中
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            synchronized (this.singletonObjects) {
        //優(yōu)先去二級緩存拿隧哮,如果沒有再去三級緩存拿。有了座舍,就直接返回沮翔。
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
            //最后一步,去三級緩存拿
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
        //調(diào)用三級緩存ObjectFactory的getObject得到提前暴露的對象曲秉。
                        singletonObject = singletonFactory.getObject();
        //放到二級緩存中采蚀,然后刪除三級緩存疲牵。可見:同一個(gè)提前暴露的bean榆鼠,只能要么在三級緩存纲爸,要么在二級緩存。
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }

我們肯定會(huì)有疑問妆够,不就去拿一個(gè)尚未初始化完成的bean對象而已嘛识啦?有一個(gè)地方存一下,這里取出來责静,不就行了嘛袁滥。為啥非要在搞一個(gè)三級緩存呢?
想知道三級緩存做了啥灾螃,就要看下三級緩存的ObjectFactory.getObject到底做了啥题翻?

三、三級緩存的ObjectFactory.getObject到底做了啥腰鬼?

提前暴露對象的代碼在doCreateBean里面嵌赠。如下:

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

        // Instantiate the bean.
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            //實(shí)例化bean,尚未初始化
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
        Class<?> beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
        mbd.resolvedTargetType = beanType;

        ..... //省略

        // Eagerly cache singletons to be able to resolve circular references
        // even when triggered by lifecycle interfaces like BeanFactoryAware.
      //判斷是否可以提前暴露熄赡。判斷條件 = 是否單例 && 是否允許(默認(rèn)true) && 是否創(chuàng)建過程中
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            if (logger.isDebugEnabled()) {
                logger.debug("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
              //這里把尚未初始化的bean姜挺,包裝成ObjectFactory對象傳遞給addSingletonFactory方法
            addSingletonFactory(beanName, new ObjectFactory<Object>() {
                @Override
                public Object getObject() throws BeansException {
                    return getEarlyBeanReference(beanName, mbd, bean);
                }
            });
        }

        // Initialize the bean instance.
        Object exposedObject = bean;
        try {
            //注入屬性
            populateBean(beanName, mbd, instanceWrapper);
            if (exposedObject != null) {
                //調(diào)用初始化方法,初始化bean
                exposedObject = initializeBean(beanName, exposedObject, mbd);
            }
        }
        ...//省略

我們看下解決循環(huán)依賴的核心方法addSingletonFactory彼硫,如下:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
        //bean已經(jīng)被其他線程初始化完成放到一級緩存了炊豪,這里也沒必要放到三級緩存
            if (!this.singletonObjects.containsKey(beanName)) {
                //放到三級緩存,然后刪除二級緩存(以防有值)
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
                this.registeredSingletons.add(beanName);
            }
        }
    }

可以看到拧篮,這里僅僅是把提前暴露的bean封裝成的ObjectFactory词渤,放到三級緩存中。

是不是還是不明白為啥需要三級緩存串绩?我們看下上面添加的匿名內(nèi)部類ObjectFactory的實(shí)現(xiàn)缺虐。

  new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
                return getEarlyBeanReference(beanName, mbd, bean);
           }
     }

答案就在getEarlyBeanReference方法里面,如下:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
    //對于有SmartInstantiationAwareBeanPostProcessor礁凡,特殊處理
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                    if (exposedObject == null) {
                        return null;
                    }
                }
            }
        }
        return exposedObject;
    }

這個(gè)地方專門用來特殊處理SmartInstantiationAwareBeanPostProcessor接口高氮,說明getEarlyBeanReference也是一個(gè)拓展點(diǎn),作用在這里的生命周期顷牌。getEarlyBeanReference的實(shí)現(xiàn)到底是啥呢剪芍?我們找個(gè)最常見的實(shí)現(xiàn)類,AbstractAutoProxyCreator窟蓝∽锕看下它的方法:

@Override
    public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            this.earlyProxyReferences.add(cacheKey);
        }
        return wrapIfNecessary(bean, beanName, cacheKey);
    }

很神奇的地方,我們在看下AbstractAutoProxyCreator.postProcessAfterInitialization方法:

@Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            if (!this.earlyProxyReferences.contains(cacheKey)) {
                return wrapIfNecessary(bean, beanName, cacheKey);
            }
        }
        return bean;
    }

getEarlyBeanReference和postProcessAfterInitialization,何其相似胺凰!;口芍!
postProcessAfterInitialization我們知道,它只會(huì)在bean的所有初始化方法完成后雇卷,調(diào)用aop生成代理類鬓椭。但是getEarlyBeanReference竟然對尚未初始化完成的bean,提前進(jìn)行了aop代理关划。Why小染?不用初始化完成,就能代理嗎贮折?

一直想不通裤翩,最后終于想通了。既然尚未初始化完成的bean都可以提前注入到其他bean里面调榄,為啥就不能提前AOP呢踊赠?我們用的是bean的引用,只要這個(gè)引用不變每庆,至于引用所指向的對象啥時(shí)候初始化完筐带,其實(shí)無所謂。其他bean也只是持有的是這個(gè)bean的引用缤灵,同理AOP代理也是僅僅持有target bean的引用伦籍。所以,所有使用到bean的地方腮出,只要實(shí)例化完成生成了引用地址帖鸦,只要這個(gè)地址不變,就可以把這個(gè)bean當(dāng)做成熟的bean使用利诺。等整個(gè)容器啟動(dòng)完成富蓄,這些bean自然而然的就初始化好了,所有引用這個(gè)bean的Bean也自然而然的就可以使用了慢逾。

image.png

到這里大家應(yīng)該清楚了立倍,為啥需要三級緩存了吧。如果你依賴的對象是AOP代理侣滩,那么就需要用到第三級緩存暫存ObjectFactory口注。

你可能又會(huì)問,為啥這里不直接把getEarlyBeanReference生成的對象君珠,放到二級緩存里面呢寝志?這樣不也節(jié)省了三級緩存嘛?為啥非要在getBean.getSingleton里面去調(diào)用getObject呢?

這個(gè)問題問得好,其實(shí)我也覺得可以材部。我目前也沒答案毫缆,可能spring基于效率的考慮吧。

個(gè)人理解:
1乐导、其實(shí)把getEarlyBeanReference生成的對象直接保存到二級緩存苦丁,無需三級緩存用ObjectFacotry封裝原始bean也可以解決循環(huán)依賴。三級緩存感覺純粹是為了延遲調(diào)用aop邏輯而已物臂。
2旺拉、其實(shí)把getEarlyBeanReference生成的對象直接暴露到一級緩存也是可以的。只要引用的地址不變棵磷,誰要用就提前給誰蛾狗。初始化動(dòng)作可以后面慢慢做。只要引用不變仪媒,它初始化完成后沉桌,所有引用它的bean都自然而然的能得到完成的該bean∷惴裕可能spring擔(dān)心一級緩存既用來存放單例bean蒲牧,又用來存放提前暴露的bean,會(huì)引起混亂赌莺。所以冰抢,上面徐庶老師說的,只要膽子大艘狭,一級緩存夠用挎扰。解決循環(huán)依賴的核心,不在乎幾級緩存巢音,而在于提前暴露引用地址即可遵倦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市官撼,隨后出現(xiàn)的幾起案子梧躺,更是在濱河造成了極大的恐慌,老刑警劉巖傲绣,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掠哥,死亡現(xiàn)場離奇詭異,居然都是意外死亡秃诵,警方通過查閱死者的電腦和手機(jī)续搀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來菠净,“玉大人禁舷,你說我怎么就攤上這事彪杉。” “怎么了牵咙?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵派近,是天一觀的道長。 經(jīng)常有香客問我洁桌,道長构哺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任战坤,我火速辦了婚禮,結(jié)果婚禮上残拐,老公的妹妹穿的比我還像新娘途茫。我一直安慰自己,他們只是感情好溪食,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布囊卜。 她就那樣靜靜地躺著,像睡著了一般错沃。 火紅的嫁衣襯著肌膚如雪栅组。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天枢析,我揣著相機(jī)與錄音玉掸,去河邊找鬼。 笑死醒叁,一個(gè)胖子當(dāng)著我的面吹牛司浪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播把沼,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼啊易,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了饮睬?” 一聲冷哼從身側(cè)響起租谈,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捆愁,沒想到半個(gè)月后割去,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡昼丑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年劫拗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矾克。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡页慷,死狀恐怖憔足,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情酒繁,我是刑警寧澤滓彰,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站州袒,受9級特大地震影響揭绑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜郎哭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一他匪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧夸研,春花似錦邦蜜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至姐扮,卻和暖如春絮供,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背茶敏。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工壤靶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惊搏。 一個(gè)月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓萍肆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親胀屿。 傳聞我的和親對象是個(gè)殘疾皇子塘揣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359