本篇內(nèi)容介紹了“怎么解決Spring循環(huán)依賴問題”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
公司主營業(yè)務(wù):網(wǎng)站設(shè)計(jì)制作、成都做網(wǎng)站、移動(dòng)網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。創(chuàng)新互聯(lián)建站是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來驚喜。創(chuàng)新互聯(lián)建站推出鳳泉免費(fèi)做網(wǎng)站回饋大家。
前言
循環(huán)依賴問題,算是一道爛大街的面試題了,解毒之前,我們先來回顧兩個(gè)知識(shí)點(diǎn):
初學(xué) Spring 的時(shí)候,我們就知道 IOC,控制反轉(zhuǎn)么,它將原本在程序中手動(dòng)創(chuàng)建對象的控制權(quán),交由 Spring 框架來管理,不需要我們手動(dòng)去各種 new XXX。
盡管是 Spring 管理,不也得創(chuàng)建對象嗎, Java 對象的創(chuàng)建步驟很多,可以 new XXX、序列化、clone() 等等, 只是 Spring 是通過反射 + 工廠的方式創(chuàng)建對象并放在容器的,創(chuàng)建好的對象我們一般還會(huì)對對象屬性進(jìn)行賦值,才去使用,可以理解是分了兩個(gè)步驟。
好了,對這兩個(gè)步驟有個(gè)印象就行,接著我們進(jìn)入循環(huán)依賴,先說下循環(huán)依賴的概念
什么是循環(huán)依賴
所謂的循環(huán)依賴是指,A 依賴 B,B 又依賴 A,它們之間形成了循環(huán)依賴。或者是 A 依賴 B,B 依賴 C,C 又依賴 A,形成了循環(huán)依賴。更或者是自己依賴自己。它們之間的依賴關(guān)系如下:
這里以兩個(gè)類直接相互依賴為例,他們的實(shí)現(xiàn)代碼可能如下:
public class BeanB { private BeanA beanA; public void setBeanA(BeanA beanA) { this.beanA = beanA; } } public class BeanA { private BeanB beanB; public void setBeanB(BeanB beanB) { this.beanB = beanB; } }
配置信息如下(用注解方式注入同理,只是為了方便理解,用了配置文件):
<bean id="beanA" class="priv.starfish.BeanA"> <property name="beanB" ref="beanB"/> </bean> <bean id="beanB" class="priv.starfish.BeanB"> <property name="beanA" ref="beanA"/> </bean>
Spring 啟動(dòng)后,讀取如上的配置文件,會(huì)按順序先實(shí)例化 A,但是創(chuàng)建的時(shí)候又發(fā)現(xiàn)它依賴了 B,接著就去實(shí)例化 B ,同樣又發(fā)現(xiàn)它依賴了 A ,這尼瑪咋整?無限循環(huán)呀
Spring “肯定”不會(huì)讓這種事情發(fā)生的,如前言我們說的 Spring 實(shí)例化對象分兩步,第一步會(huì)先創(chuàng)建一個(gè)原始對象,只是沒有設(shè)置屬性,可以理解為"半成品"—— 官方叫 A 對象的早期引用(EarlyBeanReference),所以當(dāng)實(shí)例化 B 的時(shí)候發(fā)現(xiàn)依賴了 A, B 就會(huì)把這個(gè)“半成品”設(shè)置進(jìn)去先完成實(shí)例化,既然 B 完成了實(shí)例化,所以 A 就可以獲得 B 的引用,也完成實(shí)例化了,這其實(shí)就是 Spring 解決循環(huán)依賴的思想。
不理解沒關(guān)系,先有個(gè)大概的印象,然后我們從源碼來看下 Spring 具體是怎么解決的。
源碼解毒
代碼版本:5.0.16.RELEASE
在 Spring IOC 容器讀取 Bean 配置創(chuàng)建 Bean 實(shí)例之前, 必須對它進(jìn)行實(shí)例化。只有在容器實(shí)例化后,才可以從 IOC 容器里獲取 Bean 實(shí)例并使用,循環(huán)依賴問題也就是發(fā)生在實(shí)例化 Bean 的過程中的,所以我們先回顧下獲取 Bean 的過程。
獲取 Bean 流程
Spring IOC 容器中獲取 bean 實(shí)例的簡化版流程如下(排除了各種包裝和檢查的過程)
大概的流程順序(可以結(jié)合著源碼看下,我就不貼了,貼太多的話,嘔~嘔嘔,想吐):
流程從 getBean 方法開始,getBean 是個(gè)空殼方法,所有邏輯直接到 doGetBean 方法中
transformedBeanName 將 name 轉(zhuǎn)換為真正的 beanName(name 可能是 FactoryBean 以 & 字符開頭或者有別名的情況,所以需要轉(zhuǎn)化下)
然后通過 getSingleton(beanName) 方法嘗試從緩存中查找是不是有該實(shí)例 sharedInstance(單例在 Spring 的同一容器只會(huì)被創(chuàng)建一次,后續(xù)再獲取 bean,就直接從緩存獲取即可)
如果有的話,sharedInstance 可能是完全實(shí)例化好的 bean,也可能是一個(gè)原始的 bean,所以再經(jīng) getObjectForBeanInstance 處理即可返回
當(dāng)然 sharedInstance 也可能是 null,這時(shí)候就會(huì)執(zhí)行創(chuàng)建 bean 的邏輯,將結(jié)果返回
第三步的時(shí)候我們提到了一個(gè)緩存的概念,這個(gè)就是 Spring 為了解決單例的循環(huán)依賴問題而設(shè)計(jì)的 三級緩存
/** Cache of singleton objects: bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); /** Cache of singleton factories: bean name --> ObjectFactory */ private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); /** Cache of early singleton objects: bean name --> bean instance */ private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
這三級緩存的作用分別是:
singletonObjects:完成初始化的單例對象的 cache,這里的 bean 經(jīng)歷過 實(shí)例化->屬性填充->初始化 以及各種后置處理(一級緩存)
earlySingletonObjects:存放原始的 bean 對象(完成實(shí)例化但是尚未填充屬性和初始化),僅僅能作為指針提前曝光,被其他 bean 所引用,用于解決循環(huán)依賴的 (二級緩存)
singletonFactories:在 bean 實(shí)例化完之后,屬性填充以及初始化之前,如果允許提前曝光,Spring 會(huì)將實(shí)例化后的 bean 提前曝光,也就是把該 bean 轉(zhuǎn)換成beanFactory 并加入到 singletonFactories(三級緩存)
我們首先從緩存中試著獲取 bean,就是從這三級緩存中查找
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 從 singletonObjects 獲取實(shí)例,singletonObjects 中的實(shí)例都是準(zhǔn)備好的 bean 實(shí)例,可以直接使用 Object singletonObject = this.singletonObjects.get(beanName); //isSingletonCurrentlyInCreation() 判斷當(dāng)前單例bean是否正在創(chuàng)建中 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { // 一級緩存沒有,就去二級緩存找 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 二級緩存也沒有,就去三級緩存找 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 三級緩存有的話,就把他移動(dòng)到二級緩存,.getObject() 后續(xù)會(huì)講到 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
如果緩存沒有的話,我們就要?jiǎng)?chuàng)建了,接著我們以單例對象為例,再看下創(chuàng)建 bean 的邏輯(大括號表示內(nèi)部類調(diào)用方法):
1.創(chuàng)建 bean 從以下代碼開始,一個(gè)匿名內(nèi)部類方法參數(shù)(總覺得 Lambda 的方式可讀性不如內(nèi)部類好理解)
if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); }
getSingleton() 方法內(nèi)部主要有兩個(gè)方法
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { // 創(chuàng)建 singletonObject singletonObject = singletonFactory.getObject(); // 將 singletonObject 放入緩存 addSingleton(beanName, singletonObject); }
2.getObject() 匿名內(nèi)部類的實(shí)現(xiàn)真正調(diào)用的又是 createBean(beanName, mbd, args)
3.往里走,主要的實(shí)現(xiàn)邏輯在 doCreateBean方法,先通過 createBeanInstance 創(chuàng)建一個(gè)原始 bean 對象
4.接著 addSingletonFactory 添加 bean 工廠對象到 singletonFactories 緩存(三級緩存)
5.通過 populateBean 方法向原始 bean 對象中填充屬性,并解析依賴,假設(shè)這時(shí)候創(chuàng)建 A 之后填充屬性時(shí)發(fā)現(xiàn)依賴 B,然后創(chuàng)建依賴對象 B 的時(shí)候又發(fā)現(xiàn)依賴 A,還是同樣的流程,又去 getBean(A),這個(gè)時(shí)候三級緩存已經(jīng)有了 beanA 的“半成品”,這時(shí)就可以把 A 對象的原始引用注入 B 對象(并將其移動(dòng)到二級緩存)來解決循環(huán)依賴問題。這時(shí)候 getObject() 方法就算執(zhí)行結(jié)束了,返回完全實(shí)例化的 bean
6.最后調(diào)用 addSingleton 把完全實(shí)例化好的 bean 對象放入 singletonObjects 緩存(一級緩存)中,打完收工
Spring 解決循環(huán)依賴
建議搭配著“源碼”看下邊的邏輯圖,更好下飯
流程其實(shí)上邊都已經(jīng)說過了,結(jié)合著上圖我們再看下具體細(xì)節(jié),用大白話再捋一捋:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
Spring 創(chuàng)建 bean 主要分為兩個(gè)步驟,創(chuàng)建原始 bean 對象,接著去填充對象屬性和初始化
每次創(chuàng)建 bean 之前,我們都會(huì)從緩存中查下有沒有該 bean,因?yàn)槭菃卫?,只能有一個(gè)
當(dāng)我們創(chuàng)建 beanA 的原始對象后,并把它放到三級緩存中,接下來就該填充對象屬性了,這時(shí)候發(fā)現(xiàn)依賴了 beanB,接著就又去創(chuàng)建 beanB,同樣的流程,創(chuàng)建完 beanB 填充屬性時(shí)又發(fā)現(xiàn)它依賴了 beanA,又是同樣的流程,不同的是,這時(shí)候可以在三級緩存中查到剛放進(jìn)去的原始對象 beanA,所以不需要繼續(xù)創(chuàng)建,用它注入 beanB,完成 beanB 的創(chuàng)建
既然 beanB 創(chuàng)建好了,所以 beanA 就可以完成填充屬性的步驟了,接著執(zhí)行剩下的邏輯,閉環(huán)完成
這就是單例模式下 Spring 解決循環(huán)依賴的流程了。
但是這個(gè)地方,不管是誰看源碼都會(huì)有個(gè)小疑惑,為什么需要三級緩存呢,我趕腳二級他也夠了呀
革命尚未成功,同志仍需努力
跟源碼的時(shí)候,發(fā)現(xiàn)在創(chuàng)建 beanB 需要引用 beanA 這個(gè)“半成品”的時(shí)候,就會(huì)觸發(fā)"前期引用",即如下代碼:
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 三級緩存有的話,就把他移動(dòng)到二級緩存 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); }
singletonFactory.getObject() 是一個(gè)接口方法,這里具體的實(shí)現(xiàn)方法在
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; // 這么一大段就這句話是核心,也就是當(dāng)bean要進(jìn)行提前曝光時(shí), // 給一個(gè)機(jī)會(huì),通過重寫后置處理器的getEarlyBeanReference方法,來自定義操作bean // 值得注意的是,如果提前曝光了,但是沒有被提前引用,則該后置處理器并不生效!!! // 這也正式三級緩存存在的意義,否則二級緩存就可以解決循環(huán)依賴的問題 exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName); } } } return exposedObject; }
這個(gè)方法就是 Spring 為什么使用三級緩存,而不是二級緩存的原因,它的目的是為了后置處理,如果沒有 AOP 后置處理,就不會(huì)走進(jìn) if 語句,直接返回了 exposedObject ,相當(dāng)于啥都沒干,二級緩存就夠用了。
所以又得出結(jié)論,這個(gè)三級緩存應(yīng)該和 AOP 有關(guān)系,繼續(xù)。
在 Spring 的源碼中g(shù)etEarlyBeanReference 是 SmartInstantiationAwareBeanPostProcessor接口的默認(rèn)方法,真正實(shí)現(xiàn)這個(gè)方法的只有**AbstractAutoProxyCreator** 這個(gè)類,用于提前曝光的 AOP 代理。
@Override public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { Object cacheKey = getCacheKey(bean.getClass(), beanName); this.earlyProxyReferences.put(cacheKey, bean); // 對bean進(jìn)行提前Spring AOP代理 return wrapIfNecessary(bean, beanName, cacheKey); }
這么說有點(diǎn)干,來個(gè)小 demo 吧,我們都知道 Spring AOP、事務(wù)等都是通過代理對象來實(shí)現(xiàn)的,而事務(wù)的代理對象是由自動(dòng)代理創(chuàng)建器來自動(dòng)完成的。也就是說 Spring 最終給我們放進(jìn)容器里面的是一個(gè)代理對象,而非原始對象,假設(shè)我們有如下一段業(yè)務(wù)代碼:
@Service public class HelloServiceImpl implements HelloService { @Autowired private HelloService helloService; @Override @Transactional public Object hello() { return "Hello JavaKeeper"; } }
此 Service 類使用到了事務(wù),所以最終會(huì)生成一個(gè) JDK 動(dòng)態(tài)代理對象 Proxy。剛好它又存在自己引用自己的循環(huán)依賴,完美符合我們的場景需求。
我們再自定義一個(gè)后置處理,來看下效果:
@Component public class HelloProcessor implements SmartInstantiationAwareBeanPostProcessor { @Override public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException { System.out.println("提前曝光了:"+beanName); return bean; } }
可以看到,調(diào)用方法棧中有我們自己實(shí)現(xiàn)的 HelloProcessor,說明這個(gè) bean 會(huì)通過 AOP 代理處理。
再從源碼看下這個(gè)自己循環(huán)自己的 bean 的創(chuàng)建流程:
protected Object doCreateBean( ... ){ ... boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); // 需要提前暴露(支持循環(huán)依賴),就注冊一個(gè)ObjectFactory到三級緩存 if (earlySingletonExposure) { // 添加 bean 工廠對象到 singletonFactories 緩存中,并獲取原始對象的早期引用 //匿名內(nèi)部方法 getEarlyBeanReference 就是后置處理器 // SmartInstantiationAwareBeanPostProcessor 的一個(gè)方法, // 它的功效為:保證自己被循環(huán)依賴的時(shí)候,即使被別的Bean @Autowire進(jìn)去的也是代理對象 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // 此處注意:如果此處自己被循環(huán)依賴了 那它會(huì)走上面的getEarlyBeanReference,從而創(chuàng)建一個(gè)代理對象從 三級緩存轉(zhuǎn)移到二級緩存里 // 注意此時(shí)候?qū)ο筮€在二級緩存里,并沒有在一級緩存。并且此時(shí)后續(xù)的這兩步操作還是用的 exposedObject,它仍舊是原始對象~~~ populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd); // 因?yàn)槭聞?wù)的AOP自動(dòng)代理創(chuàng)建器在getEarlyBeanReference 創(chuàng)建代理后,initializeBean 就不會(huì)再重復(fù)創(chuàng)建了,二選一的) // 所以經(jīng)過這兩大步后,exposedObject 還是原始對象,通過 getEarlyBeanReference 創(chuàng)建的代理對象還在三級緩存呢 ... // 循環(huán)依賴校驗(yàn) if (earlySingletonExposure) { // 注意此處第二個(gè)參數(shù)傳的false,表示不去三級緩存里再去調(diào)用一次getObject()方法了~~~,此時(shí)代理對象還在二級緩存,所以這里拿出來的就是個(gè) 代理對象 // 最后賦值給exposedObject 然后return出去,進(jìn)而最終被addSingleton()添加進(jìn)一級緩存里面去 // 這樣就保證了我們?nèi)萜骼?nbsp;最終實(shí)際上是代理對象,而非原始對象~~~~~ Object earlySingletonReference = getSingleton(beanName, false); if (earlySingletonReference != null) { if (exposedObject == bean) { exposedObject = earlySingletonReference; } } ... } }
自我解惑:
問:還是不太懂,為什么這么設(shè)計(jì)呢,即使有代理,在二級緩存代理也可以吧 | 為什么要使用三級緩存呢?
我們再來看下相關(guān)代碼,假設(shè)我們現(xiàn)在是二級緩存架構(gòu),創(chuàng)建 A 的時(shí)候,我們不知道有沒有循環(huán)依賴,所以放入二級緩存提前暴露,接著創(chuàng)建 B,也是放入二級緩存,這時(shí)候發(fā)現(xiàn)又循環(huán)依賴了 A,就去二級緩存找,是有,但是如果此時(shí)還有 AOP 代理呢,我們要的是代理對象可不是原始對象,這怎么辦,只能改邏輯,在第一步的時(shí)候,不管3721,所有 Bean 統(tǒng)統(tǒng)去完成 AOP 代理,如果是這樣的話,就不需要三級緩存了,但是這樣不僅沒有必要,而且違背了 Spring 在結(jié)合 AOP 跟 Bean 的生命周期的設(shè)計(jì)。
所以 Spring “多此一舉”的將實(shí)例先封裝到 ObjectFactory 中(三級緩存),主要關(guān)鍵點(diǎn)在 getObject() 方法并非直接返回實(shí)例,而是對實(shí)例又使用 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference 方法對 bean 進(jìn)行處理,也就是說,當(dāng) Spring 中存在該后置處理器,所有的單例 bean 在實(shí)例化后都會(huì)被進(jìn)行提前曝光到三級緩存中,但是并不是所有的 bean 都存在循環(huán)依賴,也就是三級緩存到二級緩存的步驟不一定都會(huì)被執(zhí)行,有可能曝光后直接創(chuàng)建完成,沒被提前引用過,就直接被加入到一級緩存中。因此可以確保只有提前曝光且被引用的 bean 才會(huì)進(jìn)行該后置處理。
protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { // 三級緩存獲取,key=beanName value=objectFactory,objectFactory中存儲(chǔ) //getObject()方法用于獲取提前曝光的實(shí)例 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { // 三級緩存有的話,就把他移動(dòng)到二級緩存 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; } boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { if (logger.isDebugEnabled()) { logger.debug("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } // 添加 bean 工廠對象到 singletonFactories 緩存中,并獲取原始對象的早期引用 //匿名內(nèi)部方法 getEarlyBeanReference 就是后置處理器 // SmartInstantiationAwareBeanPostProcessor 的一個(gè)方法, // 它的功效為:保證自己被循環(huán)依賴的時(shí)候,即使被別的Bean @Autowire進(jìn)去的也是代理對象~~~~ AOP自動(dòng)代理創(chuàng)建器此方法里會(huì)創(chuàng)建的代理對象~~~ addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
再問:AOP 代理對象提前放入了三級緩存,沒有經(jīng)過屬性填充和初始化,這個(gè)代理又是如何保證依賴屬性的注入的呢?
這個(gè)又涉及到了 Spring 中動(dòng)態(tài)代理的實(shí)現(xiàn),不管是cglib代理還是jdk動(dòng)態(tài)代理生成的代理類,代理時(shí),會(huì)將目標(biāo)對象 target 保存在最后生成的代理 $proxy 中,當(dāng)調(diào)用 $proxy 方法時(shí)會(huì)回調(diào) h.invoke,而 h.invoke 又會(huì)回調(diào)目標(biāo)對象 target 的原始方法。所有,其實(shí)在 AOP 動(dòng)態(tài)代理時(shí),原始 bean 已經(jīng)被保存在 提前曝光代理中了,之后 原始 bean 繼續(xù)完成屬性填充和初始化操作。因?yàn)?AOP 代理$proxy中保存著 traget 也就是是 原始bean 的引用,因此后續(xù) 原始bean 的完善,也就相當(dāng)于Spring AOP中的 target 的完善,這樣就保證了 AOP 的屬性填充與初始化了!
非單例循環(huán)依賴
看完了單例模式的循環(huán)依賴,我們再看下非單例的情況,假設(shè)我們的配置文件是這樣的:
<bean id="beanA" class="priv.starfish.BeanA" scope="prototype"> <property name="beanB" ref="beanB"/> </bean> <bean id="beanB" class="priv.starfish.BeanB" scope="prototype"> <property name="beanA" ref="beanA"/> </bean>
啟動(dòng) Spring,結(jié)果如下:
Error creating bean with name 'beanA' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'beanB' while setting bean property 'beanB'; Error creating bean with name 'beanB' defined in class path resource [applicationContext.xml]: Cannot resolve reference to bean 'beanA' while setting bean property 'beanA'; Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
對于 prototype 作用域的 bean,Spring 容器無法完成依賴注入,因?yàn)?Spring 容器不進(jìn)行緩存 prototype 作用域的 bean ,因此無法提前暴露一個(gè)創(chuàng)建中的bean 。
原因也挺好理解的,原型模式每次請求都會(huì)創(chuàng)建一個(gè)實(shí)例對象,即使加了緩存,循環(huán)引用太多的話,就比較麻煩了就,所以 Spring 不支持這種方式,直接拋出異常:
if (isPrototypeCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName); }
構(gòu)造器循環(huán)依賴
上文我們講的是通過 Setter 方法注入的單例 bean 的循環(huán)依賴問題,用 Spring 的小伙伴也都知道,依賴注入的方式還有構(gòu)造器注入、工廠方法注入的方式(很少使用),那如果構(gòu)造器注入方式也有循環(huán)依賴,可以搞不?
我們再改下代碼和配置文件
public class BeanA { private BeanB beanB; public BeanA(BeanB beanB) { this.beanB = beanB; } } public class BeanB { private BeanA beanA; public BeanB(BeanA beanA) { this.beanA = beanA; } }
<bean id="beanA" class="priv.starfish.BeanA"> <constructor-arg ref="beanB"/> </bean> <bean id="beanB" class="priv.starfish.BeanB"> <constructor-arg ref="beanA"/> </bean>
執(zhí)行結(jié)果,又是異常
看看官方給出的說法
Circular dependencies
If you use predominantly constructor injection, it is possible to create an unresolvable circular dependency scenario.
For example: Class A requires an instance of class B through constructor injection, and class B requires an instance of class A through constructor injection. If you configure beans for classes A and B to be injected into each other, the Spring IoC container detects this circular reference at runtime, and throws a BeanCurrentlyInCreationException.
One possible solution is to edit the source code of some classes to be configured by setters rather than constructors. Alternatively, avoid constructor injection and use setter injection only. In other words, although it is not recommended, you can configure circular dependencies with setter injection.
Unlike the typical case (with no circular dependencies), a circular dependency between bean A and bean B forces one of the beans to be injected into the other prior to being fully initialized itself (a classic chicken-and-egg scenario).
大概意思是:
如果您主要使用構(gòu)造器注入,循環(huán)依賴場景是無法解決的。建議你用 setter 注入方式代替構(gòu)造器注入
其實(shí)也不是說只要是構(gòu)造器注入就會(huì)有循環(huán)依賴問題,Spring 在創(chuàng)建 Bean 的時(shí)候默認(rèn)是按照自然排序來進(jìn)行創(chuàng)建的,我們暫且把先創(chuàng)建的 bean 叫主 bean,上文的 A 即主 bean,只要主 bean 注入依賴 bean 的方式是 setter 方式,依賴 bean 的注入方式無所謂,都可以解決,反之亦然
所以上文我們 AB 循環(huán)依賴問題,只要 A 的注入方式是 setter ,就不會(huì)有循環(huán)依賴問題。
面試官問:為什么呢?
Spring 解決循環(huán)依賴依靠的是 Bean 的“中間態(tài)”這個(gè)概念,而這個(gè)中間態(tài)指的是已經(jīng)實(shí)例化,但還沒初始化的狀態(tài)。實(shí)例化的過程又是通過構(gòu)造器創(chuàng)建的,如果 A 還沒創(chuàng)建好出來,怎么可能提前曝光,所以構(gòu)造器的循環(huán)依賴無法解決,我一直認(rèn)為應(yīng)該先有雞才能有蛋。
小總結(jié) | 面試這么答B(yǎng) 中提前注入了一個(gè)沒有經(jīng)過初始化的 A 類型對象不會(huì)有問題嗎?
雖然在創(chuàng)建 B 時(shí)會(huì)提前給 B 注入了一個(gè)還未初始化的 A 對象,但是在創(chuàng)建 A 的流程中一直使用的是注入到 B 中的 A 對象的引用,之后會(huì)根據(jù)這個(gè)引用對 A 進(jìn)行初始化,所以這是沒有問題的。
Spring 是如何解決的循環(huán)依賴?
Spring 為了解決單例的循環(huán)依賴問題,使用了三級緩存。其中一級緩存為單例池(singletonObjects),二級緩存為提前曝光對象(earlySingletonObjects),三級緩存為提前曝光對象工廠(singletonFactories)。
假設(shè)A、B循環(huán)引用,實(shí)例化 A 的時(shí)候就將其放入三級緩存中,接著填充屬性的時(shí)候,發(fā)現(xiàn)依賴了 B,同樣的流程也是實(shí)例化后放入三級緩存,接著去填充屬性時(shí)又發(fā)現(xiàn)自己依賴 A,這時(shí)候從緩存中查找到早期暴露的 A,沒有 AOP 代理的話,直接將 A 的原始對象注入 B,完成 B 的初始化后,進(jìn)行屬性填充和初始化,這時(shí)候 B 完成后,就去完成剩下的 A 的步驟,如果有 AOP 代理,就進(jìn)行 AOP 處理獲取代理后的對象 A,注入 B,走剩下的流程。
為什么要使用三級緩存呢?二級緩存能解決循環(huán)依賴嗎?
如果沒有 AOP 代理,二級緩存可以解決問題,但是有 AOP 代理的情況下,只用二級緩存就意味著所有 Bean 在實(shí)例化后就要完成 AOP 代理,這樣違背了 Spring 設(shè)計(jì)的原則,Spring 在設(shè)計(jì)之初就是通過 AnnotationAwareAspectJAutoProxyCreator 這個(gè)后置處理器來在 Bean 生命周期的最后一步來完成 AOP 代理,而不是在實(shí)例化后就立馬進(jìn)行 AOP 代理。
“怎么解決Spring循環(huán)依賴問題”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
網(wǎng)頁題目:怎么解決Spring循環(huán)依賴問題
本文網(wǎng)址:http://jinyejixie.com/article18/gdppgp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供商城網(wǎng)站、服務(wù)器托管、面包屑導(dǎo)航、移動(dòng)網(wǎng)站建設(shè)、用戶體驗(yàn)、網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)