Spring boot啟動流程之解決循環(huán)依賴的方法
循環(huán)依賴,指的是兩個bean之間相互依賴,形成了一個循環(huán)。
目前使用的spring版本中,在啟動時默認(rèn)關(guān)閉了循環(huán)依賴。假設(shè)代碼中兩個bean相互使用@Autowired注解進(jìn)行自動裝配,啟動時會報錯如下:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
翻譯過來就是:
不鼓勵使用循環(huán)引用,默認(rèn)情況下禁止使用循環(huán)引用。更新應(yīng)用程序以刪除bean之間的依賴循環(huán)。作為最后的手段,可以通過設(shè)置spring.main.allow-circular-references為true自動打破循環(huán)。
spring還是支持循環(huán)依賴的,但前提是我們要手動將其打開。我們在配置文件中設(shè)置屬性 spring.main.allow-circular-references = true。
解決循環(huán)依賴方法:
1、在bean A實(shí)例化完成后,如果檢測到支持循環(huán)依賴,會先把A緩存起來
// Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. //判斷當(dāng)前bean為單例,且允許循環(huán)依賴,且該bean正在創(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"); } addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
addSingletonFactory方法的第二個參數(shù),傳入的是一個實(shí)現(xiàn)了ObjectFactory<?>函數(shù)式接口的lambda表達(dá)式,表達(dá)式中調(diào)用的是getEarlyBeanReference方法,獲取bean的早期引用。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { /* 如果 singletonObjects 中不存在當(dāng)前bean(當(dāng)前bean還在創(chuàng)建,還沒生成最終的單例對象,顯然成立),則: 1)把當(dāng)前的singletonFactory放入singletonFactories變量中(把獲取早期引用的lambda表達(dá)式放進(jìn)三級緩存); 2)把當(dāng)前bean從earlySingletonObjects變量中移除(清理二級緩存,確保在循環(huán)引用觸發(fā)時才生成早期引用;實(shí)際這里本來也沒有); 3)把當(dāng)前beanName放入registeredSingletons變量中。 */ if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
這里可能還看不出來什么頭緒,繼續(xù)往下走,在bean A初始化的時候,會自動裝配依賴的bean B、C等,而在B、C初始化時又會自動裝配它們所依賴的A(當(dāng)然B、C在實(shí)例化之后也會和A一樣先緩存起來),但這個時候A也正在創(chuàng)建中,最終的對象肯定是拿不到的,這時候就考慮生成一個A的早期引用,先提供給B、C。
2、在doGetBean方法獲取bean A的時候,會先調(diào)用getSingleton方法查找緩存,部分源碼如下:
// Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { if (logger.isTraceEnabled()) { if (isSingletonCurrentlyInCreation(beanName)) { logger.trace("Returning eagerly cached instance of singleton bean '" + beanName + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.trace("Returning cached instance of singleton bean '" + beanName + "'"); } } beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null); }
接下來看getSingleton方法:
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock //先從singletonObjects中獲取bean A,之前說過顯然獲取不到 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { //再從earlySingletonObjects中獲取bean,剛開始也是沒有的 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { // Consistent creation of early reference within full singleton lock singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { //最后從singletonFactories里拿到singletonFactory ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //調(diào)用getObject方法獲取早期引用,放進(jìn)earlySingletonObjects,清理singletonFactories singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }
到這里三級緩存的結(jié)構(gòu)就比較清晰了:
一級緩存:singletonObject,這里存放的是最終初始化完成的bean對象。
也就是說bean初始化完成后 ,下次再拿的時候,可以直接從緩存里找,所以這是一個通用緩存,并非專門為循環(huán)依賴而設(shè)計。
二級緩存:earlySingletonObjects,存放完成了實(shí)例化,但還沒有完成初始化的半成品bean對象,其實(shí)就是相當(dāng)于把不完整的對象提前暴露了出來。
比如當(dāng)bean A在初始化注入bean B、C的時候,B、C的初始化又都依賴了A,這時候從一級緩存里找A肯定是找不到的,于是就會下探到二級緩存,如果拿到了A,doGetBean方法就會返回而不是又進(jìn)入A的初始化,相當(dāng)于這個循環(huán)被切斷了。然后B、C就能夠完成初始化,最終A也能完成初始化。
三級緩存:singletonFactories,對象工廠,存放的是獲取半成品bean對象的方法實(shí)現(xiàn)。
二級緩存的數(shù)據(jù)并不是憑空產(chǎn)生的,而是由于一開始訪問二級緩存找不到數(shù)據(jù),然后下探到三級緩存,調(diào)用了getObject方法,拿到半成品對象后才放進(jìn)去的,然后其他bean才可以直接從二級緩存里面取。
二級緩存放入數(shù)據(jù)后,三級緩存對應(yīng)的數(shù)據(jù)不再需要,立即被移除。
singletonFactory.getObject()方法其實(shí)就是lambda表達(dá)式的實(shí)現(xiàn),調(diào)用了getEarlyBeanReference方法:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }
這里調(diào)用 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference方法,獲取了bean實(shí)例的早期引用,其實(shí)就是還沒有完成初始化的半成品bean對象本身(也可能是創(chuàng)建了一個它的代理對象)。
能不能不要三級緩存,每次實(shí)例化bean之后,直接生成一個早期引用放到二級緩存中,方便循環(huán)依賴?
可以但不合理,早期引用是可以用來解決循環(huán)依賴問題,但實(shí)際上spring默認(rèn)是不推薦使用循環(huán)依賴的,如果不結(jié)合實(shí)際情況是否需要循環(huán)依賴,直接緩存一個對象,這種設(shè)計顯然有問題。
能不能不要二級緩存,每次出現(xiàn)循環(huán)依賴的時候,直接調(diào)用三級緩存的方法獲取早期引用?
不行,首先是重復(fù)調(diào)用,有可能每次獲取早期引用的工作都是完全重復(fù)的;其實(shí)如果是一個需要AOP動態(tài)代理的bean對象,每次都會產(chǎn)生一個新的代理對象,不符合單例的設(shè)計原則。
還有個細(xì)節(jié),就是當(dāng)一個bean初始化完成,拿到單例對象之后,會調(diào)用addSingleton方法:
protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
這里面不僅將對象放進(jìn)了一級緩存singletonObjects,還同時清理了對應(yīng)的二、三級緩存。
最后要注意的是,spring解決循環(huán)依賴的方式是在bean的實(shí)例化完成之后,所以不要在構(gòu)造方法中引入循環(huán)依賴,因?yàn)檫@時對象還沒有實(shí)例化,spring也無法解決。
到此這篇關(guān)于Spring boot啟動流程-解決循環(huán)依賴的文章就介紹到這了,更多相關(guān)Spring boot循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MybatisPlusInterceptor依賴變紅如何解決,無法識別問題
這篇文章主要介紹了MybatisPlusInterceptor依賴變紅如何解決,無法識別問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-07-07使用springboot logback動態(tài)獲取application的配置項
這篇文章主要介紹了使用springboot logback動態(tài)獲取application的配置項,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08Springboot整合Mybatis傳值的常用方式總結(jié)
今天給大家?guī)淼氖顷P(guān)于Springboot的相關(guān)知識,文章圍繞著Springboot整合Mybatis傳值的常用方式展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06Java讀取properties配置文件時,出現(xiàn)中文亂碼的解決方法
下面小編就為大家?guī)硪黄狫ava讀取properties配置文件時,出現(xiàn)中文亂碼的解決方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-11-11Java字符串拼接的五種方法及性能比較分析(從執(zhí)行100次到90萬次)
字符串拼接一般使用“+”,但是“+”不能滿足大批量數(shù)據(jù)的處理,Java中有以下五種方法處理字符串拼接及性能比較分析,感興趣的可以了解一下2021-12-12SWT(JFace)體驗(yàn)之StackLayout布局
SWT(JFace)體驗(yàn)之StackLayout布局實(shí)現(xiàn)代碼。2009-06-06SpringBoot使用DevTools實(shí)現(xiàn)后端熱部署的過程詳解
在Spring Boot項目中,Spring Boot官方提供你了Devtools熱部署模塊,通過maven的方式導(dǎo)入就能使用,本文主要SpringBoot通過DevTools實(shí)現(xiàn)熱部署,感興趣的朋友一起看看吧2023-11-11