Spring循環(huán)依賴產(chǎn)生與解決
循環(huán)依賴產(chǎn)生情景
探討如何解決循環(huán)依賴之前,更應(yīng)該思考清楚什么情況下會(huì)發(fā)生這種問(wèn)題?
1、模擬Prototype Bean的循環(huán)依賴
static class BeanA { // 1. 屬性循環(huán)依賴 BeanB beanB = new BeanB(); // 2. 構(gòu)造器循環(huán)依賴 public BeanA(BeanB beanB) { this.beanB = beanB; } } static class BeanB { // 1. 屬性循環(huán)依賴 BeanA beanA = new BeanA(); // 2. 構(gòu)造器循環(huán)依賴 public BeanB(BeanA beanA) { this.beanA = beanA; } } public static void main(String[] args) { // 1. 屬性循環(huán)依賴 BeanA beanA = new BeanA(); // StackOverflowError // 2. 構(gòu)造器循環(huán)依賴 new BeanA(new BeanB(new BeanA(new BeanB( /*....*/ )))); }
Prototype bean,構(gòu)造器注入Bean時(shí),強(qiáng)調(diào)注入的是成品Bean,無(wú)法解決循環(huán)依賴問(wèn)題。
Prototype bean,屬性上注入Bean時(shí),由于原型模式是單個(gè)bean可以被多次創(chuàng)建的,一旦發(fā)生循環(huán)依賴,就會(huì)產(chǎn)生上面這種不斷在創(chuàng)建新的BeanA與BeanB導(dǎo)致的棧溢出。源碼中的解決方式:記錄并檢測(cè),如下:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { // ... // Fail if we're already creating this bean instance: // We're assumably within a circular reference. if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } // ... else if (mbd.isPrototype()) { // It's a prototype -> create a new instance. Object prototypeInstance = null; try { beforePrototypeCreation(beanName); prototypeInstance = createBean(beanName, mbd, args); } finally { afterPrototypeCreation(beanName); } bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd); } // ... } protected boolean isPrototypeCurrentlyInCreation(String beanName) { Object curVal = this.prototypesCurrentlyInCreation.get(); return (curVal != null && (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName)))); } protected void beforePrototypeCreation(String beanName) { Object curVal = this.prototypesCurrentlyInCreation.get(); if (curVal == null) { this.prototypesCurrentlyInCreation.set(beanName); } else if (curVal instanceof String) { Set<String> beanNameSet = new HashSet<>(2); beanNameSet.add((String) curVal); beanNameSet.add(beanName); this.prototypesCurrentlyInCreation.set(beanNameSet); } else { Set<String> beanNameSet = (Set<String>) curVal; beanNameSet.add(beanName); } }
獲取Bean前先判斷是否當(dāng)前Bean是否為Prototype并且在創(chuàng)建中。如果不是,且當(dāng)前Bean為Prototype,那么會(huì)記錄當(dāng)前Bean正在創(chuàng)建中(通過(guò)beforePrototypeCreation方法)。
以模擬循環(huán)依賴代碼為例:
- 創(chuàng)建BeanA
- populateBean(BeanA)
- 創(chuàng)建BeanB
- populateBean(BeanB)
- 創(chuàng)建BeanA,isPrototypeCurrentlyInCreation發(fā)現(xiàn)BeanA正常創(chuàng)建中,拋出BeanCurrentlyInCreationException
2、模擬Singleton Bean的循環(huán)依賴
static class BeanA { BeanB beanB; public BeanA(BeanB beanB) { this.beanB = beanB; } } static class BeanB { BeanA beanA; public BeanB(BeanA beanA) { this.beanA = beanA; } } public static void main(String[] args) { // 1. 屬性循環(huán)依賴 BeanA beanA = new BeanA(); BeanB beanB = new BeanB(); beanA.beanB = beanB; beanB.beanA = beanA; // 2. 構(gòu)造器循環(huán)依賴 new BeanA(new BeanB(new BeanA(new BeanB( /*....*/ )))); }
Singleton bean,構(gòu)造器注入Bean時(shí),強(qiáng)調(diào)注入的是成品Bean,無(wú)法解決循環(huán)依賴問(wèn)題。
Singleton bean,屬性上注入Bean時(shí),允許注入提前暴露的半成品的Bean,即沒(méi)有填充屬性&初始化的Bean,只要引用關(guān)系能關(guān)聯(lián)上,屬性填充與初始化隨著程序的執(zhí)行自然就會(huì)處理完成,可以解決循環(huán)依賴。
為什么構(gòu)造器中不能先傳遞一個(gè)半成品Bean,然后賦值給成員Bean屬性呢?而一定要傳遞一個(gè)成品Bean?很好理解,spring并不能確定用戶在構(gòu)造器中的操作僅為賦值給Bean屬性。且很多時(shí)候,我們需要通過(guò)bean的一些注入的Bean屬性來(lái)執(zhí)行一些業(yè)務(wù)操作的。如果為空,那么就可能會(huì)發(fā)生空指針異常。而且這樣也不是特別合理。若用戶選擇屬性注入的方式,用戶不會(huì)涉及這些操作,那么自然就不需要考慮這些問(wèn)題。
3、總結(jié)成一句話來(lái)說(shuō):spring僅能解決單例Bean下發(fā)生在屬性上注入Bean產(chǎn)生的循環(huán)依賴。
Spring如何解決循環(huán)依賴
通過(guò)三級(jí)緩存解決循環(huán)依賴,分別是:
1、singletonObjects: 一級(jí)緩存,存儲(chǔ)成品Bean
2、earlySingletonObjects: 二級(jí)緩存,存儲(chǔ)半成品Bean
3、singletonFactories: 三級(jí)緩存,存儲(chǔ)生成半成品Bean的ObjectFactory的lambda
還是以BeanA注入BeanB,BeanB注入BeanA為例,描述一下大致的執(zhí)行流程:
- 檢查BeanA的緩存信息
- 反射實(shí)例化BeanA
- 注冊(cè)暴露 BeanA的lambda(生成放入二級(jí)緩存中的半成品BeanA)到三級(jí)緩存:addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))
- 填充屬性BeanB:populateBean
- 檢查BeanB的緩存信息
- 注冊(cè)暴露 BeanB的lambda(生成放入二級(jí)緩存中的半成品BeanB)到三級(jí)緩存
- 反射實(shí)例化BeanB
- 填充屬性BeanA:populateBean
- 檢查BeanA的緩存信息,從三級(jí)緩存中獲取到了生成半成品BeanA的lambda,執(zhí)行獲取半成品BeanA并放入二級(jí)緩存,并在三級(jí)緩存中移除lambda
- 將生成的BeanA的二級(jí)緩存對(duì)象賦值到BeanB的屬性上
- 將BeanB放入一級(jí)緩存,并在二、三級(jí)緩存中清理關(guān)于BeanB的記錄
- 初始化BeanB
- 返回到第四步,將成品BeanB賦值到BeanA的屬性上
- 將BeanA放入一級(jí)緩存,并在二、三級(jí)緩存中清理關(guān)于BeanA的記錄
- 初始化BeanA
思考一下,是否能通過(guò)二級(jí)緩存來(lái)解決循環(huán)依賴?
首先spring對(duì)于三級(jí)緩存的應(yīng)用,就是在生成需要提前暴露的半成品Bean,要么返回的是通過(guò)反射實(shí)例化后的對(duì)象,要么是被一組SmartInstantiationAwareBeanPostProcessor處理后的對(duì)象(比如生成代理Bean),然后在放到二級(jí)緩存中。那么我們?cè)诜湃攵?jí)緩存時(shí),不通過(guò)三級(jí)緩存獲取這個(gè)過(guò)程,直接在方法中復(fù)現(xiàn)這個(gè)過(guò)程,在放入二級(jí)緩存中,效果想必也是相同的。
到此這篇關(guān)于Spring循環(huán)依賴產(chǎn)生與解決的文章就介紹到這了,更多相關(guān)Spring循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Kafka分區(qū)發(fā)送及消費(fèi)實(shí)戰(zhàn)
本文主要介紹了Kafka分區(qū)發(fā)送及消費(fèi)實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07IntelliJ IDEA2019 安裝lombok的實(shí)現(xiàn)
這篇文章主要介紹了IntelliJ IDEA2019 安裝lombok的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10java獲取當(dāng)前時(shí)間的四種方法代碼實(shí)例
這篇文章主要介紹了java獲取當(dāng)前時(shí)間的四種方法代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09使用JDBC4.0操作XML類型的字段(保存獲取xml數(shù)據(jù))的方法
jdbc4.0最重要的特征是支持xml數(shù)據(jù)類型,接下來(lái)通過(guò)本文重點(diǎn)給大家介紹如何使用jdbc4.0操作xml類型的字段,對(duì)jdbc4.0 xml相關(guān)知識(shí)感興趣的朋友一起看下吧2016-08-08Java的接口調(diào)用時(shí)的權(quán)限驗(yàn)證功能的實(shí)現(xiàn)
這篇文章主要介紹了Java的接口調(diào)用時(shí)的權(quán)限驗(yàn)證功能的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11MyBatisPlus代碼生成器的原理及實(shí)現(xiàn)詳解
這篇文章主要為大家詳細(xì)介紹了MyBatisPlus中代碼生成器的原理及實(shí)現(xiàn),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)MyBatisPlus有一定幫助,需要的可以參考一下2022-08-08