Spring源碼之循環(huán)依賴之三級緩存詳解
循環(huán)依賴
定義
循環(huán)依賴就 循環(huán)引用,就是兩個或多個 bean 相互之間的持有對方,比如 CircleA 引用 CircleB , CircleB 引用 CircleC, CircleC 引用 CircleA ,則它們最終反映為 個環(huán)。此處不是循環(huán)調(diào)用,循環(huán)調(diào)用是方法之間的環(huán)調(diào)用。
循環(huán)調(diào)用是無法解決的,除非有終結(jié)條件,否則就是死循環(huán),最終導(dǎo)致內(nèi)存溢出錯誤
三種循環(huán)依賴的情況
Spring容器將每一個正在創(chuàng)建的bean標(biāo)識符放在一個“當(dāng)前創(chuàng)建bean池”中,bean標(biāo)識符在創(chuàng)建過程中將一直保持在這個池中,因此如果在創(chuàng)建bean過程中發(fā)現(xiàn)自己已經(jīng)在“當(dāng)前創(chuàng)建bean池”里時,將拋出BeanCurrentlylnCreationException異常表示循環(huán)依賴;而對于創(chuàng)建完畢的bean將從“當(dāng)前創(chuàng)建bean池"中清除掉。
1.構(gòu)造器循環(huán)依賴
表示通過構(gòu)造器注入構(gòu)成的循環(huán)依賴,此依賴是無法解決的,只能拋出異常
2.settler循環(huán)依賴
表示通過setter注入方式構(gòu)成的循環(huán)依賴。對于setter注入造成的依賴是通過Spring容器提前暴露剛完成構(gòu)造器注入但未完成其他步驟(如setter注人)的bean來完成的,而且只能解決單例作用域的bean循環(huán)依賴。通過提前暴露一個單例工廠方法,從而使其他bean能引用到該bean。
3.prototype范圍的依賴處理
對于"prototype"作用域bean,Spring容器無法完成依賴注人,因為Spring容器不進(jìn)行緩存"prototype"作用域的bean,因此無法提前暴露一個創(chuàng)建中的bean。
三級緩存機(jī)制
- 定義 一級緩存用于存放已經(jīng)實例化、初始化完成的Bean,
單例池-singletonObjects
- 二級緩存用于存放已經(jīng)實例化,但
未初始化的Bean
.保證一個類多次循環(huán)依賴時僅構(gòu)建一次保證單例提前曝光早產(chǎn)bean池-earlySingletonObjects
- 三級緩存用于存放該Bean的BeanFactory,當(dāng)加載一個Bean會先將該Bean包裝為BeanFactory放入三級緩存
早期單例bean工廠池-singletonFactories
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry { /** Cache of singleton objects: bean name --> bean instance */ //用于存放完全初始化好的 bean從該緩存中取出的 bean可以直接使用 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name --> ObjectFactory */ //存放 bean工廠對象解決循環(huán)依賴 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); //存放原始的bean對象用于解決循環(huán)依賴,注意:存到里面的對象還沒有被填充屬性 /** Cache of early singleton objects: bean name --> bean instance */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); }
整體分析
創(chuàng)建Bean會先將該Bean的BeanFactory放到三級緩存中,以用來防止循環(huán)依賴問題.當(dāng)存在有A,B兩個Bean循環(huán)依賴時,創(chuàng)建流程如下
1.先創(chuàng)建BeanA,先實例化BeanA并包裝為BeanFactory并放入三級緩存中.
2.給BeanA進(jìn)行屬性填充時檢查依賴,發(fā)現(xiàn)BeanB未加載過,則先去加載BeanB
3.BeanB創(chuàng)建過程首先也要包裝成BeanFactory放到三級緩存,填充屬性時則是從三級緩存獲取Bean將BeanA填充進(jìn)去
4.。BeanB填充BeanA從三級緩存中的BeanAFacotry獲取BeanA
5.獲取主要通過ObjectFactory.getObject方法,該方法調(diào)用getEarlyBeanReference方法,他會創(chuàng)建Bean/Bean的代理并刪除BeanA的三級緩存,加入二級緩存
6.BeanB初始化完畢加入一級緩存,BeanA繼續(xù)執(zhí)行初始化,初始化完畢比較BeanA二級緩存和一級緩存是否一致,一致則加入一級緩存刪除二級緩存
源碼分析
此處以A、B類的互相依賴注入為例,在這里表達(dá)出關(guān)鍵代碼的走勢:
1.入口處即是實例化、初始化A這個單例Bean。AbstractBeanFactory.doGetBean("a")
protected <T> T doGetBean(...){ ... // 標(biāo)記beanName a是已經(jīng)創(chuàng)建過至少一次的~~~ 它會一直存留在緩存里不會被移除(除非拋出了異常) // 參見緩存Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256)) if (!typeCheckOnly) { markBeanAsCreated(beanName); } // 此時a不存在任何一級緩存中,且不是在創(chuàng)建中 所以此處返回null // 此處若不為null,然后從緩存里拿就可以了(主要處理FactoryBean和BeanFactory情況吧) Object beanInstance = getSingleton(beanName, false); ... // 這個getSingleton方法非常關(guān)鍵。 //1、標(biāo)注a正在創(chuàng)建中~ //2、調(diào)用singletonObject = singletonFactory.getObject();(實際上調(diào)用的是createBean()方法) 因此這一步最為關(guān)鍵 //3、此時實例已經(jīng)創(chuàng)建完成 會把a(bǔ)移除整整創(chuàng)建的緩存中 //4、執(zhí)行addSingleton()添加進(jìn)去。(備注:注冊bean的接口方法為registerSingleton,它依賴于addSingleton方法) sharedInstance = getSingleton(beanName, () -> { ... return createBean(beanName, mbd, args); }); }
下面進(jìn)入到最為復(fù)雜的AbstractAutowireCapableBeanFactory.createBean/doCreateBean()
環(huán)節(jié),創(chuàng)建A的實例
protected Object doCreateBean(){ ... // 使用構(gòu)造器/工廠方法 instanceWrapper是一個BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args); // 此處bean為"原始Bean" 也就是這里的A實例對象:A@1234 final Object bean = instanceWrapper.getWrappedInstance(); ... // 是否要提前暴露(允許循環(huán)依賴) 現(xiàn)在此處A是被允許的 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); // 允許暴露,就把A綁定在ObjectFactory上,注冊到三級緩存`singletonFactories`里面去保存著 // Tips:這里后置處理器的getEarlyBeanReference方法會被促發(fā),自動代理創(chuàng)建器在此處創(chuàng)建代理對象(注意執(zhí)行時機(jī) 為執(zhí)行三級緩存的時候) if (earlySingletonExposure) { addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } ... // exposedObject 為最終返回的對象,此處為原始對象bean也就是A@1234,下面會有用處 Object exposedObject = bean; // 給A@1234屬性完成賦值,@Autowired在此處起作用~ // 因此此處會調(diào)用getBean("b"),so 會重復(fù)上面步驟創(chuàng)建B類的實例 // 此處我們假設(shè)B已經(jīng)創(chuàng)建好了 為B@5678 // 需要注意的是在populateBean("b")的時候依賴有beanA,所以此時候調(diào)用getBean("a")最終會調(diào)用getSingleton("a"), //此時候上面說到的getEarlyBeanReference方法就會被執(zhí)行。這也解釋為何我們@Autowired是個代理對象,而不是普通對象的根本原因 populateBean(beanName, mbd, instanceWrapper); // 實例化。這里會執(zhí)行后置處理器BeanPostProcessor的兩個方法 // 此處注意:postProcessAfterInitialization()是有可能返回一個代理對象的,這樣exposedObject 就不再是原始對象了 exposedObject = initializeBean(beanName, exposedObject, mbd); ... // 至此,相當(dāng)于A@1234已經(jīng)實例化完成、初始化完成(屬性也全部賦值了~) // 這一步我把它理解為校驗:校驗:校驗是否有循環(huán)引用問題~~~~~ if (earlySingletonExposure) { // 注意此處第二個參數(shù)傳的false,表示不去三級緩存里singletonFactories再去調(diào)用一次getObject()方法了~~~ // 上面建講到了由于B在初始化的時候,會觸發(fā)A的ObjectFactory.getObject() 所以a此處已經(jīng)在二級緩存earlySingletonObjects里了 // 因此此處返回A的實例:A@1234 Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { // 這個等式表示,exposedObject若沒有再被代理過,這里就是相等的 // 顯然此處我們的a對象的exposedObject它是沒有被代理過的 所以if會進(jìn)去~ // 這種情況至此,就全部結(jié)束了~~~ if (exposedObject == bean) { exposedObject = earlySingletonReference; } // 繼續(xù)以A為例,比如方法標(biāo)注了@Aysnc注解,exposedObject此時候就是一個代理對象,因此就會進(jìn)到這里來 //hasDependentBean(beanName)是肯定為true,因為getDependentBeans(beanName)得到的是["b"]這個依賴 else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) { String[] dependentBeans = getDependentBeans(beanName); Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length); // A@1234依賴的是["b"],所以此處去檢查b // 如果最終存在實際依賴的bean:actualDependentBeans不為空 那就拋出異常 證明循環(huán)引用了~ for (String dependentBean : dependentBeans) { // 這個判斷原則是:如果此時候b并還沒有創(chuàng)建好,this.alreadyCreated.contains(beanName)=true表示此bean已經(jīng)被創(chuàng)建過,就返回false // 若該bean沒有在alreadyCreated緩存里,就是說沒被創(chuàng)建過(其實只有CreatedForTypeCheckOnly才會是此倉庫) 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."); } } } } }
依舊以上面A、B類使用屬性field注入循環(huán)依賴的例子為例,對整個流程做文字步驟總結(jié)如下:
1.使用context.getBean(A.class),旨在獲取容器內(nèi)的單例A(若A不存在,就會走A這個Bean的創(chuàng)建流程),顯然初次獲取A是不存在的,因此走A的創(chuàng)建之路~
2.實例化A(注意此處僅僅是實例化),并將它放進(jìn)緩存(此時A已經(jīng)實例化完成,已經(jīng)可以被引用了)
3.初始化A:@Autowired依賴注入B(此時需要去容器內(nèi)獲取B)
4.為了完成依賴注入B,會通過getBean(B)去容器內(nèi)找B。但此時B在容器內(nèi)不存在,就走向B的創(chuàng)建之路~
5.實例化B,并將其放入緩存。(此時B也能夠被引用了)
6.初始化B,@Autowired依賴注入A(此時需要去容器內(nèi)獲取A)
7.此處重要:初始化B時會調(diào)用getBean(A)去容器內(nèi)找到A,上面我們已經(jīng)說過了此時候因為A已經(jīng)實例化完成了并且放進(jìn)了緩存里,所以這個時候去看緩存里是已經(jīng)存在A的引用了的,所以getBean(A)能夠正常返回
8.B初始化成功(此時已經(jīng)注入A成功了,已成功持有A的引用了),return(注意此處return相當(dāng)于是返回最上面的getBean(B)這句代碼,回到了初始化A的流程中~)。
9.因為B實例已經(jīng)成功返回了,因此最終A也初始化成功
10.到此,B持有的已經(jīng)是初始化完成的A,A持有的也是初始化完成的B
面試題
Spring為什么定三級緩存(二級緩存也可以解決循環(huán)依賴的問題)
首先當(dāng)Bean未有循環(huán)依賴三級緩存是沒有什么意義的,當(dāng)有循環(huán)依賴但Bean并沒有AOP代理,則會直接返回原對象,也沒有什么意義。主要在當(dāng)Bean存在循環(huán)依賴并且還有AOP代理時,三級緩存才有效果,三級緩存主要預(yù)防Bean有依賴時還可以完成代理增強(qiáng)
而本身Spring設(shè)計Bean的代理增強(qiáng)是在Bean初始化完成后的AnnotationAwareAspectJ***Creator后置處理器中完成的。提前執(zhí)行則和設(shè)計思路相違背。所以三級緩存主要起預(yù)防循環(huán)依賴作用,可能是一個補(bǔ)丁機(jī)制
參考鏈接:
你知道怎么用Spring的三級緩存解決循環(huán)依賴嗎
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Struts1教程之ActionMapping_動力節(jié)點Java學(xué)院整理
這篇文章主要介紹了Struts1教程之ActionMapping,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09Java對象和Json文本轉(zhuǎn)換工具類的實現(xiàn)
Json?是一個用于Java對象和Json文本相互轉(zhuǎn)換的工具類,本文主要介紹了Java對象和Json文本轉(zhuǎn)換工具類,具有一定的參考價值,感興趣的可以了解一下2022-03-03springboot2如何集成ElasticSearch6.4.3
這篇文章主要介紹了springboot2如何集成ElasticSearch6.4.3問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07spring,mybatis事務(wù)管理配置與@Transactional注解使用詳解
這篇文章主要介紹了spring,mybatis事務(wù)管理配置與@Transactional注解使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07Java spring boot 實現(xiàn)支付寶支付功能的示例代碼
這篇文章主要介紹了Java spring boot 實現(xiàn)支付寶支付功能,本文通過實例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06java String 類的一些理解 關(guān)于==、equals、null
在對字符串的相等判斷,==判斷的是地址是否相同,equal()判斷的是字符值是否相同。大多數(shù)時候==跟equal()的結(jié)果都是相同的。2009-06-06Spring Boot與RabbitMQ結(jié)合實現(xiàn)延遲隊列的示例
本篇文章主要介紹了Spring Boot與RabbitMQ結(jié)合實現(xiàn)延遲隊列的示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11Spring Boot實現(xiàn)Undertow服務(wù)器同時支持HTTP2、HTTPS的方法
這篇文章考慮如何讓Spring Boot應(yīng)用程序同時支持HTTP和HTTPS兩種協(xié)議。小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12