Spring源碼之循環(huán)依賴(lài)之三級(jí)緩存詳解
循環(huán)依賴(lài)
定義
循環(huán)依賴(lài)就 循環(huán)引用,就是兩個(gè)或多個(gè) bean 相互之間的持有對(duì)方,比如 CircleA 引用 CircleB , CircleB 引用 CircleC, CircleC 引用 CircleA ,則它們最終反映為 個(gè)環(huán)。此處不是循環(huán)調(diào)用,循環(huán)調(diào)用是方法之間的環(huán)調(diào)用。
循環(huán)調(diào)用是無(wú)法解決的,除非有終結(jié)條件,否則就是死循環(huán),最終導(dǎo)致內(nèi)存溢出錯(cuò)誤
三種循環(huán)依賴(lài)的情況
Spring容器將每一個(gè)正在創(chuàng)建的bean標(biāo)識(shí)符放在一個(gè)“當(dāng)前創(chuàng)建bean池”中,bean標(biāo)識(shí)符在創(chuàng)建過(guò)程中將一直保持在這個(gè)池中,因此如果在創(chuàng)建bean過(guò)程中發(fā)現(xiàn)自己已經(jīng)在“當(dāng)前創(chuàng)建bean池”里時(shí),將拋出BeanCurrentlylnCreationException異常表示循環(huán)依賴(lài);而對(duì)于創(chuàng)建完畢的bean將從“當(dāng)前創(chuàng)建bean池"中清除掉。
1.構(gòu)造器循環(huán)依賴(lài)
表示通過(guò)構(gòu)造器注入構(gòu)成的循環(huán)依賴(lài),此依賴(lài)是無(wú)法解決的,只能拋出異常
2.settler循環(huán)依賴(lài)
表示通過(guò)setter注入方式構(gòu)成的循環(huán)依賴(lài)。對(duì)于setter注入造成的依賴(lài)是通過(guò)Spring容器提前暴露剛完成構(gòu)造器注入但未完成其他步驟(如setter注人)的bean來(lái)完成的,而且只能解決單例作用域的bean循環(huán)依賴(lài)。通過(guò)提前暴露一個(gè)單例工廠方法,從而使其他bean能引用到該bean。
3.prototype范圍的依賴(lài)處理
對(duì)于"prototype"作用域bean,Spring容器無(wú)法完成依賴(lài)注人,因?yàn)镾pring容器不進(jìn)行緩存"prototype"作用域的bean,因此無(wú)法提前暴露一個(gè)創(chuàng)建中的bean。
三級(jí)緩存機(jī)制
- 定義 一級(jí)緩存用于存放已經(jīng)實(shí)例化、初始化完成的Bean,
單例池-singletonObjects - 二級(jí)緩存用于存放已經(jīng)實(shí)例化,但
未初始化的Bean.保證一個(gè)類(lèi)多次循環(huán)依賴(lài)時(shí)僅構(gòu)建一次保證單例提前曝光早產(chǎn)bean池-earlySingletonObjects - 三級(jí)緩存用于存放該Bean的BeanFactory,當(dāng)加載一個(gè)Bean會(huì)先將該Bean包裝為BeanFactory放入三級(jí)緩存
早期單例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工廠對(duì)象解決循環(huán)依賴(lài)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//存放原始的bean對(duì)象用于解決循環(huán)依賴(lài),注意:存到里面的對(duì)象還沒(méi)有被填充屬性
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
}

整體分析
創(chuàng)建Bean會(huì)先將該Bean的BeanFactory放到三級(jí)緩存中,以用來(lái)防止循環(huán)依賴(lài)問(wèn)題.當(dāng)存在有A,B兩個(gè)Bean循環(huán)依賴(lài)時(shí),創(chuàng)建流程如下
1.先創(chuàng)建BeanA,先實(shí)例化BeanA并包裝為BeanFactory并放入三級(jí)緩存中.
2.給BeanA進(jìn)行屬性填充時(shí)檢查依賴(lài),發(fā)現(xiàn)BeanB未加載過(guò),則先去加載BeanB
3.BeanB創(chuàng)建過(guò)程首先也要包裝成BeanFactory放到三級(jí)緩存,填充屬性時(shí)則是從三級(jí)緩存獲取Bean將BeanA填充進(jìn)去
4.。BeanB填充BeanA從三級(jí)緩存中的BeanAFacotry獲取BeanA
5.獲取主要通過(guò)ObjectFactory.getObject方法,該方法調(diào)用getEarlyBeanReference方法,他會(huì)創(chuàng)建Bean/Bean的代理并刪除BeanA的三級(jí)緩存,加入二級(jí)緩存
6.BeanB初始化完畢加入一級(jí)緩存,BeanA繼續(xù)執(zhí)行初始化,初始化完畢比較BeanA二級(jí)緩存和一級(jí)緩存是否一致,一致則加入一級(jí)緩存刪除二級(jí)緩存

源碼分析
此處以A、B類(lèi)的互相依賴(lài)注入為例,在這里表達(dá)出關(guān)鍵代碼的走勢(shì):
1.入口處即是實(shí)例化、初始化A這個(gè)單例Bean。AbstractBeanFactory.doGetBean("a")
protected <T> T doGetBean(...){
...
// 標(biāo)記beanName a是已經(jīng)創(chuàng)建過(guò)至少一次的~~~ 它會(huì)一直存留在緩存里不會(huì)被移除(除非拋出了異常)
// 參見(jiàn)緩存Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256))
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
// 此時(shí)a不存在任何一級(jí)緩存中,且不是在創(chuàng)建中 所以此處返回null
// 此處若不為null,然后從緩存里拿就可以了(主要處理FactoryBean和BeanFactory情況吧)
Object beanInstance = getSingleton(beanName, false);
...
// 這個(gè)getSingleton方法非常關(guān)鍵。
//1、標(biāo)注a正在創(chuàng)建中~
//2、調(diào)用singletonObject = singletonFactory.getObject();(實(shí)際上調(diào)用的是createBean()方法) 因此這一步最為關(guān)鍵
//3、此時(shí)實(shí)例已經(jīng)創(chuàng)建完成 會(huì)把a(bǔ)移除整整創(chuàng)建的緩存中
//4、執(zhí)行addSingleton()添加進(jìn)去。(備注:注冊(cè)bean的接口方法為registerSingleton,它依賴(lài)于addSingleton方法)
sharedInstance = getSingleton(beanName, () -> { ... return createBean(beanName, mbd, args); });
}
下面進(jìn)入到最為復(fù)雜的AbstractAutowireCapableBeanFactory.createBean/doCreateBean()環(huán)節(jié),創(chuàng)建A的實(shí)例
protected Object doCreateBean(){
...
// 使用構(gòu)造器/工廠方法 instanceWrapper是一個(gè)BeanWrapper
instanceWrapper = createBeanInstance(beanName, mbd, args);
// 此處bean為"原始Bean" 也就是這里的A實(shí)例對(duì)象:A@1234
final Object bean = instanceWrapper.getWrappedInstance();
...
// 是否要提前暴露(允許循環(huán)依賴(lài)) 現(xiàn)在此處A是被允許的
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
// 允許暴露,就把A綁定在ObjectFactory上,注冊(cè)到三級(jí)緩存`singletonFactories`里面去保存著
// Tips:這里后置處理器的getEarlyBeanReference方法會(huì)被促發(fā),自動(dòng)代理創(chuàng)建器在此處創(chuàng)建代理對(duì)象(注意執(zhí)行時(shí)機(jī) 為執(zhí)行三級(jí)緩存的時(shí)候)
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
...
// exposedObject 為最終返回的對(duì)象,此處為原始對(duì)象bean也就是A@1234,下面會(huì)有用處
Object exposedObject = bean;
// 給A@1234屬性完成賦值,@Autowired在此處起作用~
// 因此此處會(huì)調(diào)用getBean("b"),so 會(huì)重復(fù)上面步驟創(chuàng)建B類(lèi)的實(shí)例
// 此處我們假設(shè)B已經(jīng)創(chuàng)建好了 為B@5678
// 需要注意的是在populateBean("b")的時(shí)候依賴(lài)有beanA,所以此時(shí)候調(diào)用getBean("a")最終會(huì)調(diào)用getSingleton("a"),
//此時(shí)候上面說(shuō)到的getEarlyBeanReference方法就會(huì)被執(zhí)行。這也解釋為何我們@Autowired是個(gè)代理對(duì)象,而不是普通對(duì)象的根本原因
populateBean(beanName, mbd, instanceWrapper);
// 實(shí)例化。這里會(huì)執(zhí)行后置處理器BeanPostProcessor的兩個(gè)方法
// 此處注意:postProcessAfterInitialization()是有可能返回一個(gè)代理對(duì)象的,這樣exposedObject 就不再是原始對(duì)象了
exposedObject = initializeBean(beanName, exposedObject, mbd);
... // 至此,相當(dāng)于A@1234已經(jīng)實(shí)例化完成、初始化完成(屬性也全部賦值了~)
// 這一步我把它理解為校驗(yàn):校驗(yàn):校驗(yàn)是否有循環(huán)引用問(wèn)題~~~~~
if (earlySingletonExposure) {
// 注意此處第二個(gè)參數(shù)傳的false,表示不去三級(jí)緩存里singletonFactories再去調(diào)用一次getObject()方法了~~~
// 上面建講到了由于B在初始化的時(shí)候,會(huì)觸發(fā)A的ObjectFactory.getObject() 所以a此處已經(jīng)在二級(jí)緩存earlySingletonObjects里了
// 因此此處返回A的實(shí)例:A@1234
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 這個(gè)等式表示,exposedObject若沒(méi)有再被代理過(guò),這里就是相等的
// 顯然此處我們的a對(duì)象的exposedObject它是沒(méi)有被代理過(guò)的 所以if會(huì)進(jìn)去~
// 這種情況至此,就全部結(jié)束了~~~
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// 繼續(xù)以A為例,比如方法標(biāo)注了@Aysnc注解,exposedObject此時(shí)候就是一個(gè)代理對(duì)象,因此就會(huì)進(jìn)到這里來(lái)
//hasDependentBean(beanName)是肯定為true,因?yàn)間etDependentBeans(beanName)得到的是["b"]這個(gè)依賴(lài)
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
// A@1234依賴(lài)的是["b"],所以此處去檢查b
// 如果最終存在實(shí)際依賴(lài)的bean:actualDependentBeans不為空 那就拋出異常 證明循環(huán)引用了~
for (String dependentBean : dependentBeans) {
// 這個(gè)判斷原則是:如果此時(shí)候b并還沒(méi)有創(chuàng)建好,this.alreadyCreated.contains(beanName)=true表示此bean已經(jīng)被創(chuàng)建過(guò),就返回false
// 若該bean沒(méi)有在alreadyCreated緩存里,就是說(shuō)沒(méi)被創(chuàng)建過(guò)(其實(shí)只有CreatedForTypeCheckOnly才會(huì)是此倉(cāng)庫(kù))
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類(lèi)使用屬性field注入循環(huán)依賴(lài)的例子為例,對(duì)整個(gè)流程做文字步驟總結(jié)如下:
1.使用context.getBean(A.class),旨在獲取容器內(nèi)的單例A(若A不存在,就會(huì)走A這個(gè)Bean的創(chuàng)建流程),顯然初次獲取A是不存在的,因此走A的創(chuàng)建之路~
2.實(shí)例化A(注意此處僅僅是實(shí)例化),并將它放進(jìn)緩存(此時(shí)A已經(jīng)實(shí)例化完成,已經(jīng)可以被引用了)
3.初始化A:@Autowired依賴(lài)注入B(此時(shí)需要去容器內(nèi)獲取B)
4.為了完成依賴(lài)注入B,會(huì)通過(guò)getBean(B)去容器內(nèi)找B。但此時(shí)B在容器內(nèi)不存在,就走向B的創(chuàng)建之路~
5.實(shí)例化B,并將其放入緩存。(此時(shí)B也能夠被引用了)
6.初始化B,@Autowired依賴(lài)注入A(此時(shí)需要去容器內(nèi)獲取A)
7.此處重要:初始化B時(shí)會(huì)調(diào)用getBean(A)去容器內(nèi)找到A,上面我們已經(jīng)說(shuō)過(guò)了此時(shí)候因?yàn)锳已經(jīng)實(shí)例化完成了并且放進(jìn)了緩存里,所以這個(gè)時(shí)候去看緩存里是已經(jīng)存在A的引用了的,所以getBean(A)能夠正常返回
8.B初始化成功(此時(shí)已經(jīng)注入A成功了,已成功持有A的引用了),return(注意此處return相當(dāng)于是返回最上面的getBean(B)這句代碼,回到了初始化A的流程中~)。
9.因?yàn)锽實(shí)例已經(jīng)成功返回了,因此最終A也初始化成功
10.到此,B持有的已經(jīng)是初始化完成的A,A持有的也是初始化完成的B
面試題
Spring為什么定三級(jí)緩存(二級(jí)緩存也可以解決循環(huán)依賴(lài)的問(wèn)題)
首先當(dāng)Bean未有循環(huán)依賴(lài)三級(jí)緩存是沒(méi)有什么意義的,當(dāng)有循環(huán)依賴(lài)但Bean并沒(méi)有AOP代理,則會(huì)直接返回原對(duì)象,也沒(méi)有什么意義。主要在當(dāng)Bean存在循環(huán)依賴(lài)并且還有AOP代理時(shí),三級(jí)緩存才有效果,三級(jí)緩存主要預(yù)防Bean有依賴(lài)時(shí)還可以完成代理增強(qiáng)
而本身Spring設(shè)計(jì)Bean的代理增強(qiáng)是在Bean初始化完成后的AnnotationAwareAspectJ***Creator后置處理器中完成的。提前執(zhí)行則和設(shè)計(jì)思路相違背。所以三級(jí)緩存主要起預(yù)防循環(huán)依賴(lài)作用,可能是一個(gè)補(bǔ)丁機(jī)制
參考鏈接:
你知道怎么用Spring的三級(jí)緩存解決循環(huán)依賴(lài)嗎
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
- 淺談Spring 解決循環(huán)依賴(lài)必須要三級(jí)緩存嗎
- 關(guān)于Java Spring三級(jí)緩存和循環(huán)依賴(lài)的深入理解
- Spring解決循環(huán)依賴(lài)的方法(三級(jí)緩存)
- 關(guān)于Spring中一級(jí)緩存、二級(jí)緩存和三級(jí)緩存的那些事
- 你知道怎么用Spring的三級(jí)緩存解決循環(huán)依賴(lài)嗎
- 教你Spring如何使用三級(jí)緩存解決循環(huán)依賴(lài)
- Java中通過(guò)三級(jí)緩存解決Spring循環(huán)依賴(lài)詳解
- Spring實(shí)現(xiàn)三級(jí)緩存機(jī)制
相關(guān)文章
Java SimpleDateFormat中英文時(shí)間格式化轉(zhuǎn)換詳解
這篇文章主要為大家詳細(xì)介紹了Java SimpleDateFormat中英文時(shí)間格式化轉(zhuǎn)換,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
SpringBoot使用Thymeleaf自定義標(biāo)簽的實(shí)例代碼
這篇文章主要介紹了SpringBoot使用Thymeleaf自定義標(biāo)簽的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
springboot解決前后端分離時(shí)的跨域問(wèn)題
這篇文章主要介紹了springboot如何解決前后端分離時(shí)的跨域問(wèn)題,幫助大家更好的理解和學(xué)習(xí)使用springboot,感興趣的朋友可以了解下2021-04-04
Springboot如何通過(guò)自定義工具類(lèi)獲取bean
這篇文章主要介紹了Springboot通過(guò)自定義工具類(lèi)獲取bean方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
Spring Cloud @RefreshScope 原理及使用
這篇文章主要介紹了Spring Cloud @RefreshScope 原理及使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
SpringBoot整合MongoDB實(shí)現(xiàn)事務(wù)管理
Spring Boot是一種快速開(kāi)發(fā)Spring應(yīng)用的方式,它提供了大量的自動(dòng)配置和默認(rèn)設(shè)置,以簡(jiǎn)化開(kāi)發(fā)流程,MongoDB是一個(gè)基于文檔的NoSQL數(shù)據(jù)庫(kù),本文將介紹如何在Spring Boot應(yīng)用中整合MongoDB,并實(shí)現(xiàn)事務(wù)管理,需要的朋友可以參考下2024-07-07
基于Java實(shí)現(xiàn)中文分詞系統(tǒng)的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用Java語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)易的中文分詞系統(tǒng),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以嘗試一下2022-07-07

