1. 面試:講一講Spring中的循環(huán)依賴

        共 11553字,需瀏覽 24分鐘

         ·

        2020-08-11 22:59

        前言

        Spring中的循環(huán)依賴一直是Spring中一個(gè)很重要的話題,一方面是因?yàn)樵创a中為了解決循環(huán)依賴做了很多處理,另外一方面是因?yàn)槊嬖嚨臅r(shí)候,如果問(wèn)到Spring中比較高階的問(wèn)題,那么循環(huán)依賴必定逃不掉。如果你回答得好,那么這就是你的必殺技,反正,那就是面試官的必殺技,這也是取這個(gè)標(biāo)題的原因,當(dāng)然,本文的目的是為了讓你在之后的所有面試中能多一個(gè)必殺技,專門(mén)用來(lái)絕殺面試官!

        本文的核心思想就是,

        當(dāng)面試官問(wèn):

        “請(qǐng)講一講Spring中的循環(huán)依賴。”的時(shí)候,

        我們到底該怎么回答?

        主要分下面幾點(diǎn)

        1. 什么是循環(huán)依賴?
        2. 什么情況下循環(huán)依賴可以被處理?
        3. Spring是如何解決的循環(huán)依賴?

        同時(shí)本文希望糾正幾個(gè)目前業(yè)界內(nèi)經(jīng)常出現(xiàn)的幾個(gè)關(guān)于循環(huán)依賴的錯(cuò)誤的說(shuō)法

        1. 只有在setter方式注入的情況下,循環(huán)依賴才能解決(錯(cuò)
        2. 三級(jí)緩存的目的是為了提高效率(錯(cuò)

        OK,鋪墊已經(jīng)做完了,接下來(lái)我們開(kāi)始正文

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

        從字面上來(lái)理解就是A依賴B的同時(shí)B也依賴了A,就像下面這樣

        image-20200705175322521

        體現(xiàn)到代碼層次就是這個(gè)樣子

        @Component
        public?class?A?{
        ????//?A中注入了B
        ?@Autowired
        ?private?B?b;
        }

        @Component
        public?class?B?{
        ????//?B中也注入了A
        ?@Autowired
        ?private?A?a;
        }

        當(dāng)然,這是最常見(jiàn)的一種循環(huán)依賴,比較特殊的還有

        //?自己依賴自己
        @Component
        public?class?A?{
        ????//?A中注入了A
        ?@Autowired
        ?private?A?a;
        }

        雖然體現(xiàn)形式不一樣,但是實(shí)際上都是同一個(gè)問(wèn)題----->循環(huán)依賴

        什么情況下循環(huán)依賴可以被處理?

        在回答這個(gè)問(wèn)題之前首先要明確一點(diǎn),Spring解決循環(huán)依賴是有前置條件的

        1. 出現(xiàn)循環(huán)依賴的Bean必須要是單例
        2. 依賴注入的方式不能全是構(gòu)造器注入的方式(很多博客上說(shuō),只能解決setter方法的循環(huán)依賴,這是錯(cuò)誤的)

        其中第一點(diǎn)應(yīng)該很好理解,第二點(diǎn):不能全是構(gòu)造器注入是什么意思呢?我們還是用代碼說(shuō)話

        @Component
        public?class?A?{
        //?@Autowired
        //?private?B?b;
        ?public?A(B?b)?{

        ?}
        }


        @Component
        public?class?B?{

        //?@Autowired
        //?private?A?a;

        ?public?B(A?a){

        ?}
        }

        在上面的例子中,A中注入B的方式是通過(guò)構(gòu)造器,B中注入A的方式也是通過(guò)構(gòu)造器,這個(gè)時(shí)候循環(huán)依賴是無(wú)法被解決,如果你的項(xiàng)目中有兩個(gè)這樣相互依賴的Bean,在啟動(dòng)時(shí)就會(huì)報(bào)出以下錯(cuò)誤:

        Caused?by:?org.springframework.beans.factory.BeanCurrentlyInCreationException:?Error?creating?bean?with?name?'a':?Requested?bean?is?currently?in?creation:?Is?there?an?unresolvable?circular?reference?

        為了測(cè)試循環(huán)依賴的解決情況跟注入方式的關(guān)系,我們做如下四種情況的測(cè)試

        依賴情況依賴注入方式循環(huán)依賴是否被解決
        AB相互依賴(循環(huán)依賴)均采用setter方法注入
        AB相互依賴(循環(huán)依賴)均采用構(gòu)造器注入
        AB相互依賴(循環(huán)依賴)A中注入B的方式為setter方法,B中注入A的方式為構(gòu)造器
        AB相互依賴(循環(huán)依賴)B中注入A的方式為setter方法,A中注入B的方式為構(gòu)造器

        具體的測(cè)試代碼很簡(jiǎn)單,我就不放了。從上面的測(cè)試結(jié)果我們可以看到,不是只有在setter方法注入的情況下循環(huán)依賴才能被解決,即使存在構(gòu)造器注入的場(chǎng)景下,循環(huán)依賴依然被可以被正常處理掉。

        那么到底是為什么呢?Spring到底是怎么處理的循環(huán)依賴呢?不要急,我們接著往下看

        Spring是如何解決的循環(huán)依賴?

        關(guān)于循環(huán)依賴的解決方式應(yīng)該要分兩種情況來(lái)討論

        1. 簡(jiǎn)單的循環(huán)依賴(沒(méi)有AOP)
        2. 結(jié)合了AOP的循環(huán)依賴

        簡(jiǎn)單的循環(huán)依賴(沒(méi)有AOP)

        我們先來(lái)分析一個(gè)最簡(jiǎn)單的例子,就是上面提到的那個(gè)demo

        @Component
        public?class?A?{
        ????//?A中注入了B
        ?@Autowired
        ?private?B?b;
        }

        @Component
        public?class?B?{
        ????//?B中也注入了A
        ?@Autowired
        ?private?A?a;
        }

        通過(guò)上文我們已經(jīng)知道了這種情況下的循環(huán)依賴是能夠被解決的,那么具體的流程是什么呢?我們一步步分析

        首先,我們要知道Spring在創(chuàng)建Bean的時(shí)候默認(rèn)是按照自然排序來(lái)進(jìn)行創(chuàng)建的,所以第一步Spring會(huì)去創(chuàng)建A

        與此同時(shí),我們應(yīng)該知道,Spring在創(chuàng)建Bean的過(guò)程中分為三步

        1. 實(shí)例化,對(duì)應(yīng)方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法

        2. 屬性注入,對(duì)應(yīng)方法:AbstractAutowireCapableBeanFactorypopulateBean方法

        3. 初始化,對(duì)應(yīng)方法:AbstractAutowireCapableBeanFactoryinitializeBean

        這些方法在之前源碼分析的文章中都做過(guò)詳細(xì)的解讀了,如果你之前沒(méi)看過(guò)我的文章,那么你只需要知道

        1. 實(shí)例化,簡(jiǎn)單理解就是new了一個(gè)對(duì)象
        2. 屬性注入,為實(shí)例化中new出來(lái)的對(duì)象填充屬性
        3. 初始化,執(zhí)行aware接口中的方法,初始化方法,完成AOP代理

        基于上面的知識(shí),我們開(kāi)始解讀整個(gè)循環(huán)依賴處理的過(guò)程,整個(gè)流程應(yīng)該是以A的創(chuàng)建為起點(diǎn),前文也說(shuō)了,第一步就是創(chuàng)建A嘛!


        創(chuàng)建A的過(guò)程實(shí)際上就是調(diào)用getBean方法,這個(gè)方法有兩層含義

        1. 創(chuàng)建一個(gè)新的Bean
        2. 從緩存中獲取到已經(jīng)被創(chuàng)建的對(duì)象

        我們現(xiàn)在分析的是第一層含義,因?yàn)檫@個(gè)時(shí)候緩存中還沒(méi)有A嘛!

        調(diào)用getSingleton(beanName)

        首先調(diào)用getSingleton(a)方法,這個(gè)方法又會(huì)調(diào)用getSingleton(beanName, true),在上圖中我省略了這一步

        public?Object?getSingleton(String?beanName)?{
        ????return?getSingleton(beanName,?true);
        }

        getSingleton(beanName, true)這個(gè)方法實(shí)際上就是到緩存中嘗試去獲取Bean,整個(gè)緩存分為三級(jí)

        1. singletonObjects,一級(jí)緩存,存儲(chǔ)的是所有創(chuàng)建好了的單例Bean
        2. earlySingletonObjects,完成實(shí)例化,但是還未進(jìn)行屬性注入及初始化的對(duì)象
        3. singletonFactories,提前暴露的一個(gè)單例工廠,二級(jí)緩存中存儲(chǔ)的就是從這個(gè)工廠中獲取到的對(duì)象

        因?yàn)锳是第一次被創(chuàng)建,所以不管哪個(gè)緩存中必然都是沒(méi)有的,因此會(huì)進(jìn)入getSingleton的另外一個(gè)重載方法getSingleton(beanName, singletonFactory)。

        調(diào)用getSingleton(beanName, singletonFactory)

        這個(gè)方法就是用來(lái)創(chuàng)建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)?{

        ????????????//?....
        ????????????//?省略異常處理及日志
        ????????????//?....

        ????????????//?在單例對(duì)象創(chuàng)建前先做一個(gè)標(biāo)記
        ????????????//?將beanName放入到singletonsCurrentlyInCreation這個(gè)集合中
        ????????????//?標(biāo)志著這個(gè)單例Bean正在創(chuàng)建
        ????????????//?如果同一個(gè)單例Bean多次被創(chuàng)建,這里會(huì)拋出異常
        ????????????beforeSingletonCreation(beanName);
        ????????????boolean?newSingleton?=?false;
        ????????????boolean?recordSuppressedExceptions?=?(this.suppressedExceptions?==?null);
        ????????????if?(recordSuppressedExceptions)?{
        ????????????????this.suppressedExceptions?=?new?LinkedHashSet<>();
        ????????????}
        ????????????try?{
        ????????????????//?上游傳入的lambda在這里會(huì)被執(zhí)行,調(diào)用createBean方法創(chuàng)建一個(gè)Bean后返回
        ????????????????singletonObject?=?singletonFactory.getObject();
        ????????????????newSingleton?=?true;
        ????????????}
        ????????????//?...
        ????????????//?省略catch異常處理
        ????????????//?...
        ????????????finally?{
        ????????????????if?(recordSuppressedExceptions)?{
        ????????????????????this.suppressedExceptions?=?null;
        ????????????????}
        ????????????????//?創(chuàng)建完成后將對(duì)應(yīng)的beanName從singletonsCurrentlyInCreation移除
        ????????????????afterSingletonCreation(beanName);
        ????????????}
        ????????????if?(newSingleton)?{
        ????????????????//?添加到一級(jí)緩存singletonObjects中
        ????????????????addSingleton(beanName,?singletonObject);
        ????????????}
        ????????}
        ????????return?singletonObject;
        ????}
        }

        上面的代碼我們主要抓住一點(diǎn),通過(guò)createBean方法返回的Bean最終被放到了一級(jí)緩存,也就是單例池中。

        那么到這里我們可以得出一個(gè)結(jié)論:一級(jí)緩存中存儲(chǔ)的是已經(jīng)完全創(chuàng)建好了的單例Bean

        調(diào)用addSingletonFactory方法

        如下圖所示:


        在完成Bean的實(shí)例化后,屬性注入之前Spring將Bean包裝成一個(gè)工廠添加進(jìn)了三級(jí)緩存中,對(duì)應(yīng)源碼如下:

        //?這里傳入的參數(shù)也是一個(gè)lambda表達(dá)式,()?->?getEarlyBeanReference(beanName,?mbd,?bean)
        protected?void?addSingletonFactory(String?beanName,?ObjectFactory?singletonFactory)?{
        ????Assert.notNull(singletonFactory,?"Singleton?factory?must?not?be?null");
        ????synchronized?(this.singletonObjects)?{
        ????????if?(!this.singletonObjects.containsKey(beanName))?{
        ????????????//?添加到三級(jí)緩存中
        ????????????this.singletonFactories.put(beanName,?singletonFactory);
        ????????????this.earlySingletonObjects.remove(beanName);
        ????????????this.registeredSingletons.add(beanName);
        ????????}
        ????}
        }

        這里只是添加了一個(gè)工廠,通過(guò)這個(gè)工廠(ObjectFactory)的getObject方法可以得到一個(gè)對(duì)象,而這個(gè)對(duì)象實(shí)際上就是通過(guò)getEarlyBeanReference這個(gè)方法創(chuàng)建的。那么,什么時(shí)候會(huì)去調(diào)用這個(gè)工廠的getObject方法呢?這個(gè)時(shí)候就要到創(chuàng)建B的流程了。

        當(dāng)A完成了實(shí)例化并添加進(jìn)了三級(jí)緩存后,就要開(kāi)始為A進(jìn)行屬性注入了,在注入時(shí)發(fā)現(xiàn)A依賴了B,那么這個(gè)時(shí)候Spring又會(huì)去getBean(b),然后反射調(diào)用setter方法完成屬性注入。


        因?yàn)锽需要注入A,所以在創(chuàng)建B的時(shí)候,又會(huì)去調(diào)用getBean(a),這個(gè)時(shí)候就又回到之前的流程了,但是不同的是,之前的getBean是為了創(chuàng)建Bean,而此時(shí)再調(diào)用getBean不是為了創(chuàng)建了,而是要從緩存中獲取,因?yàn)橹癆在實(shí)例化后已經(jīng)將其放入了三級(jí)緩存singletonFactories中,所以此時(shí)getBean(a)的流程就是這樣子了


        從這里我們可以看出,注入到B中的A是通過(guò)getEarlyBeanReference方法提前暴露出去的一個(gè)對(duì)象,還不是一個(gè)完整的Bean,那么getEarlyBeanReference到底干了啥了,我們看下它的源碼

        protected?Object?getEarlyBeanReference(String?beanName,?RootBeanDefinition?mbd,?Object?bean)?{
        ????Object?exposedObject?=?bean;
        ????if?(!mbd.isSynthetic()?&&?hasInstantiationAwareBeanPostProcessors())?{
        ????????for?(BeanPostProcessor?bp?:?getBeanPostProcessors())?{
        ????????????if?(bp?instanceof?SmartInstantiationAwareBeanPostProcessor)?{
        ????????????????SmartInstantiationAwareBeanPostProcessor?ibp?=?(SmartInstantiationAwareBeanPostProcessor)?bp;
        ????????????????exposedObject?=?ibp.getEarlyBeanReference(exposedObject,?beanName);
        ????????????}
        ????????}
        ????}
        ????return?exposedObject;
        }

        它實(shí)際上就是調(diào)用了后置處理器的getEarlyBeanReference,而真正實(shí)現(xiàn)了這個(gè)方法的后置處理器只有一個(gè),就是通過(guò)@EnableAspectJAutoProxy注解導(dǎo)入的AnnotationAwareAspectJAutoProxyCreator。也就是說(shuō)如果在不考慮AOP的情況下,上面的代碼等價(jià)于:

        protected?Object?getEarlyBeanReference(String?beanName,?RootBeanDefinition?mbd,?Object?bean)?{
        ????Object?exposedObject?=?bean;
        ????return?exposedObject;
        }

        也就是說(shuō)這個(gè)工廠啥都沒(méi)干,直接將實(shí)例化階段創(chuàng)建的對(duì)象返回了!所以說(shuō)在不考慮AOP的情況下三級(jí)緩存有用嘛?講道理,真的沒(méi)什么用,我直接將這個(gè)對(duì)象放到二級(jí)緩存中不是一點(diǎn)問(wèn)題都沒(méi)有嗎?如果你說(shuō)它提高了效率,那你告訴我提高的效率在哪?


        那么三級(jí)緩存到底有什么作用呢?不要急,我們先把整個(gè)流程走完,在下文結(jié)合AOP分析循環(huán)依賴的時(shí)候你就能體會(huì)到三級(jí)緩存的作用!

        到這里不知道小伙伴們會(huì)不會(huì)有疑問(wèn),B中提前注入了一個(gè)沒(méi)有經(jīng)過(guò)初始化的A類型對(duì)象不會(huì)有問(wèn)題嗎?

        答:不會(huì)

        這個(gè)時(shí)候我們需要將整個(gè)創(chuàng)建A這個(gè)Bean的流程走完,如下圖:


        從上圖中我們可以看到,雖然在創(chuàng)建B時(shí)會(huì)提前給B注入了一個(gè)還未初始化的A對(duì)象,但是在創(chuàng)建A的流程中一直使用的是注入到B中的A對(duì)象的引用,之后會(huì)根據(jù)這個(gè)引用對(duì)A進(jìn)行初始化,所以這是沒(méi)有問(wèn)題的。

        結(jié)合了AOP的循環(huán)依賴

        之前我們已經(jīng)說(shuō)過(guò)了,在普通的循環(huán)依賴的情況下,三級(jí)緩存沒(méi)有任何作用。三級(jí)緩存實(shí)際上跟Spring中的AOP相關(guān),我們?cè)賮?lái)看一看getEarlyBeanReference的代碼:

        protected?Object?getEarlyBeanReference(String?beanName,?RootBeanDefinition?mbd,?Object?bean)?{
        ????Object?exposedObject?=?bean;
        ????if?(!mbd.isSynthetic()?&&?hasInstantiationAwareBeanPostProcessors())?{
        ????????for?(BeanPostProcessor?bp?:?getBeanPostProcessors())?{
        ????????????if?(bp?instanceof?SmartInstantiationAwareBeanPostProcessor)?{
        ????????????????SmartInstantiationAwareBeanPostProcessor?ibp?=?(SmartInstantiationAwareBeanPostProcessor)?bp;
        ????????????????exposedObject?=?ibp.getEarlyBeanReference(exposedObject,?beanName);
        ????????????}
        ????????}
        ????}
        ????return?exposedObject;
        }

        如果在開(kāi)啟AOP的情況下,那么就是調(diào)用到AnnotationAwareAspectJAutoProxyCreatorgetEarlyBeanReference方法,對(duì)應(yīng)的源碼如下:

        public?Object?getEarlyBeanReference(Object?bean,?String?beanName)?{
        ????Object?cacheKey?=?getCacheKey(bean.getClass(),?beanName);
        ????this.earlyProxyReferences.put(cacheKey,?bean);
        ????//?如果需要代理,返回一個(gè)代理對(duì)象,不需要代理,直接返回當(dāng)前傳入的這個(gè)bean對(duì)象
        ????return?wrapIfNecessary(bean,?beanName,?cacheKey);
        }

        回到上面的例子,我們對(duì)A進(jìn)行了AOP代理的話,那么此時(shí)getEarlyBeanReference將返回一個(gè)代理后的對(duì)象,而不是實(shí)例化階段創(chuàng)建的對(duì)象,這樣就意味著B(niǎo)中注入的A將是一個(gè)代理對(duì)象而不是A的實(shí)例化階段創(chuàng)建后的對(duì)象。

        看到這個(gè)圖你可能會(huì)產(chǎn)生下面這些疑問(wèn)

        1. 在給B注入的時(shí)候?yàn)槭裁匆⑷胍粋€(gè)代理對(duì)象?

        答:當(dāng)我們對(duì)A進(jìn)行了AOP代理時(shí),說(shuō)明我們希望從容器中獲取到的就是A代理后的對(duì)象而不是A本身,因此把A當(dāng)作依賴進(jìn)行注入時(shí)也要注入它的代理對(duì)象

        1. 明明初始化的時(shí)候是A對(duì)象,那么Spring是在哪里將代理對(duì)象放入到容器中的呢?


        在完成初始化后,Spring又調(diào)用了一次getSingleton方法,這一次傳入的參數(shù)又不一樣了,false可以理解為禁用三級(jí)緩存,前面圖中已經(jīng)提到過(guò)了,在為B中注入A時(shí)已經(jīng)將三級(jí)緩存中的工廠取出,并從工廠中獲取到了一個(gè)對(duì)象放入到了二級(jí)緩存中,所以這里的這個(gè)getSingleton方法做的時(shí)間就是從二級(jí)緩存中獲取到這個(gè)代理后的A對(duì)象。exposedObject == bean可以認(rèn)為是必定成立的,除非你非要在初始化階段的后置處理器中替換掉正常流程中的Bean,例如增加一個(gè)后置處理器:

        @Component
        public?class?MyPostProcessor?implements?BeanPostProcessor?{
        ?@Override
        ?public?Object?postProcessAfterInitialization(Object?bean,?String?beanName)?throws?BeansException?{
        ??if?(beanName.equals("a"))?{
        ???return?new?A();
        ??}
        ??return?bean;
        ?}
        }

        不過(guò),請(qǐng)不要做這種騷操作,徒增煩惱!

        1. 初始化的時(shí)候是對(duì)A對(duì)象本身進(jìn)行初始化,而容器中以及注入到B中的都是代理對(duì)象,這樣不會(huì)有問(wèn)題嗎?

        答:不會(huì),這是因?yàn)椴还苁?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;border-radius: 4px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(255, 93, 108);">cglib代理還是jdk動(dòng)態(tài)代理生成的代理類,內(nèi)部都持有一個(gè)目標(biāo)類的引用,當(dāng)調(diào)用代理對(duì)象的方法時(shí),實(shí)際會(huì)去調(diào)用目標(biāo)對(duì)象的方法,A完成初始化相當(dāng)于代理對(duì)象自身也完成了初始化

        1. 三級(jí)緩存為什么要使用工廠而不是直接使用引用?換而言之,為什么需要這個(gè)三級(jí)緩存,直接通過(guò)二級(jí)緩存暴露一個(gè)引用不行嗎?

        答:這個(gè)工廠的目的在于延遲對(duì)實(shí)例化階段生成的對(duì)象的代理,只有真正發(fā)生循環(huán)依賴的時(shí)候,才去提前生成代理對(duì)象,否則只會(huì)創(chuàng)建一個(gè)工廠并將其放入到三級(jí)緩存中,但是不會(huì)去通過(guò)這個(gè)工廠去真正創(chuàng)建對(duì)象

        我們思考一種簡(jiǎn)單的情況,就以單獨(dú)創(chuàng)建A為例,假設(shè)AB之間現(xiàn)在沒(méi)有依賴關(guān)系,但是A被代理了,這個(gè)時(shí)候當(dāng)A完成實(shí)例化后還是會(huì)進(jìn)入下面這段代碼:

        //?A是單例的,mbd.isSingleton()條件滿足
        // allowCircularReferences:這個(gè)變量代表是否允許循環(huán)依賴,默認(rèn)是開(kāi)啟的,條件也滿足
        // isSingletonCurrentlyInCreation:正在在創(chuàng)建A,也滿足
        //?所以earlySingletonExposure=true
        boolean?earlySingletonExposure?=?(mbd.isSingleton()?&&?this.allowCircularReferences?&&
        ??????????????????????????????????isSingletonCurrentlyInCreation(beanName));
        //?還是會(huì)進(jìn)入到這段代碼中
        if?(earlySingletonExposure)?{
        ?//?還是會(huì)通過(guò)三級(jí)緩存提前暴露一個(gè)工廠對(duì)象
        ????addSingletonFactory(beanName,?()?->?getEarlyBeanReference(beanName,?mbd,?bean));
        }

        看到了吧,即使沒(méi)有循環(huán)依賴,也會(huì)將其添加到三級(jí)緩存中,而且是不得不添加到三級(jí)緩存中,因?yàn)榈侥壳盀橹筍pring也不能確定這個(gè)Bean有沒(méi)有跟別的Bean出現(xiàn)循環(huán)依賴。

        假設(shè)我們?cè)谶@里直接使用二級(jí)緩存的話,那么意味著所有的Bean在這一步都要完成AOP代理。這樣做有必要嗎?

        不僅沒(méi)有必要,而且違背了Spring在結(jié)合AOP跟Bean的生命周期的設(shè)計(jì)!Spring結(jié)合AOP跟Bean的生命周期本身就是通過(guò)AnnotationAwareAspectJAutoProxyCreator這個(gè)后置處理器來(lái)完成的,在這個(gè)后置處理的postProcessAfterInitialization方法中對(duì)初始化后的Bean完成AOP代理。如果出現(xiàn)了循環(huán)依賴,那沒(méi)有辦法,只有給Bean先創(chuàng)建代理,但是沒(méi)有出現(xiàn)循環(huán)依賴的情況下,設(shè)計(jì)之初就是讓Bean在生命周期的最后一步完成代理而不是在實(shí)例化后就立馬完成代理。

        三級(jí)緩存真的提高了效率了嗎?

        現(xiàn)在我們已經(jīng)知道了三級(jí)緩存的真正作用,但是這個(gè)答案可能還無(wú)法說(shuō)服你,所以我們?cè)僮詈罂偨Y(jié)分析一波,三級(jí)緩存真的提高了效率了嗎?分為兩點(diǎn)討論:

        1. 沒(méi)有進(jìn)行AOP的Bean間的循環(huán)依賴

        從上文分析可以看出,這種情況下三級(jí)緩存根本沒(méi)用!所以不會(huì)存在什么提高了效率的說(shuō)法

        1. 進(jìn)行了AOP的Bean間的循環(huán)依賴

        就以我們上的A、B為例,其中A被AOP代理,我們先分析下使用了三級(jí)緩存的情況下,A、B的創(chuàng)建流程


        假設(shè)不使用三級(jí)緩存,直接在二級(jí)緩存中


        上面兩個(gè)流程的唯一區(qū)別在于為A對(duì)象創(chuàng)建代理的時(shí)機(jī)不同,在使用了三級(jí)緩存的情況下為A創(chuàng)建代理的時(shí)機(jī)是在B中需要注入A的時(shí)候,而不使用三級(jí)緩存的話在A實(shí)例化后就需要馬上為A創(chuàng)建代理然后放入到二級(jí)緩存中去。對(duì)于整個(gè)A、B的創(chuàng)建過(guò)程而言,消耗的時(shí)間是一樣的

        綜上,不管是哪種情況,三級(jí)緩存提高了效率這種說(shuō)法都是錯(cuò)誤的!

        總結(jié)

        面試官:”Spring是如何解決的循環(huán)依賴?“

        答:Spring通過(guò)三級(jí)緩存解決了循環(huán)依賴,其中一級(jí)緩存為單例池(singletonObjects),二級(jí)緩存為早期曝光對(duì)象earlySingletonObjects,三級(jí)緩存為早期曝光對(duì)象工廠(singletonFactories)。當(dāng)A、B兩個(gè)類發(fā)生循環(huán)引用時(shí),在A完成實(shí)例化后,就使用實(shí)例化后的對(duì)象去創(chuàng)建一個(gè)對(duì)象工廠,并添加到三級(jí)緩存中,如果A被AOP代理,那么通過(guò)這個(gè)工廠獲取到的就是A代理后的對(duì)象,如果A沒(méi)有被AOP代理,那么這個(gè)工廠獲取到的就是A實(shí)例化的對(duì)象。當(dāng)A進(jìn)行屬性注入時(shí),會(huì)去創(chuàng)建B,同時(shí)B又依賴了A,所以創(chuàng)建B的同時(shí)又會(huì)去調(diào)用getBean(a)來(lái)獲取需要的依賴,此時(shí)的getBean(a)會(huì)從緩存中獲取,第一步,先獲取到三級(jí)緩存中的工廠;第二步,調(diào)用對(duì)象工工廠的getObject方法來(lái)獲取到對(duì)應(yīng)的對(duì)象,得到這個(gè)對(duì)象后將其注入到B中。緊接著B(niǎo)會(huì)走完它的生命周期流程,包括初始化、后置處理器等。當(dāng)B創(chuàng)建完后,會(huì)將B再注入到A中,此時(shí)A再完成它的整個(gè)生命周期。至此,循環(huán)依賴結(jié)束!

        面試官:”為什么要使用三級(jí)緩存呢?二級(jí)緩存能解決循環(huán)依賴嗎?“

        答:如果要使用二級(jí)緩存解決循環(huán)依賴,意味著所有Bean在實(shí)例化后就要完成AOP代理,這樣違背了Spring設(shè)計(jì)的原則,Spring在設(shè)計(jì)之初就是通過(guò)AnnotationAwareAspectJAutoProxyCreator這個(gè)后置處理器來(lái)在Bean生命周期的最后一步來(lái)完成AOP代理,而不是在實(shí)例化后就立馬進(jìn)行AOP代理。

        一道思考題

        為什么在下表中的第三種情況的循環(huán)依賴能被解決,而第四種情況不能被解決呢?

        提示:Spring在創(chuàng)建Bean時(shí)默認(rèn)會(huì)根據(jù)自然排序進(jìn)行創(chuàng)建,所以A會(huì)先于B進(jìn)行創(chuàng)建

        依賴情況依賴注入方式循環(huán)依賴是否被解決
        AB相互依賴(循環(huán)依賴)均采用setter方法注入
        AB相互依賴(循環(huán)依賴)均采用構(gòu)造器注入
        AB相互依賴(循環(huán)依賴)A中注入B的方式為setter方法,B中注入A的方式為構(gòu)造器
        AB相互依賴(循環(huán)依賴)B中注入A的方式為setter方法,A中注入B的方式為構(gòu)造器


        往期推薦

        公司用了 6 年的分布式鎖,很是牛逼?。?/p>

        IDEA不愧為神器,結(jié)合Groovy腳本,簡(jiǎn)直無(wú)敵!

        只需4步,自己搞個(gè) Spring Boot Starter !

        說(shuō)說(shuō)你知道的數(shù)據(jù)庫(kù)常用架構(gòu)方案?

        ArrayList 為什么要實(shí)現(xiàn) RandomAccess 接口?



        每日學(xué)干貨,一起進(jìn)大廠

        關(guān)注我,不迷路

        瀏覽 37
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 久久性爱小说 | 草草影院第一页YYCCC | 国产精品日韩无码有码 | 日本性爱一区二区三区 | 奇米影视第四色7777 |