摘要:我們知道為我們完全實例化好一個一定會經過一下三步實例化,其實也就是調用對象的構造方法實例化對象。循環依賴的產生定會發生在步驟和中,因為是利用構造方法,是利用屬性賦值。
引言
基于Spring5+
什么是循環依賴?
循環依賴有幾種?
Spring可以解決哪幾種,為什么不能解決這幾種?
Spring是如何判斷存在循環依賴的?
什么是循環依賴?什么是循環依賴?我們都知道Spring最大的作用就是來替我們管理Bean的,當然也包括Bean的創建以及整個生命周期,但是有這么一種情況,假設有三個類A、B、C需要交給Spring來管理,但A實例的創建需要先有B實例,而B實例的創建需要先有C實例,C實例的創建需要先有A實例,這樣三個類就自然形成了一個環狀結構,如果用代碼來表示,如下:
public class TestA { TestB testB; get; set; } public class TestB { TestC testC; get; set; } public class TestC { TestA testA; get; set; }
這樣,三個類就彼此形成了一個環狀,那么Spring是如何來處理這樣的狀況呢?循環依賴有幾種?
有三種情況:
基于構造方法的循環依賴
基于setter構造的循環依賴(網上也叫field屬性依賴)
基于prototype范圍的依賴
Spring可以解決哪些循環依賴,為什么?首先說一下結論:除了第二種Spring可以幫我們解決,其它兩種都不能解決。我們知道Spring為我們完全實例化好一個Bean一定會經過一下三步:
createBeanInstance:實例化,其實也就是調用對象的構造方法實例化對象。
populateBean:填充屬性,這一步主要是多bean的依賴屬性進行填充。
initializeBean:調用默認的或者自定義的init方法。
循環依賴的產生定會發生在步驟1和2中,因為1是利用構造方法,2是利用屬性賦值。
基于構造方法的循環依賴先說結論基于構造器的循環依賴Spring是無法解決的,是因為沒有加入提前曝光的集合中,加入集合的條件是已經創建了Bean的包裝對象,而構造注入的時候,并沒有完成對象的創建,下面會有代碼說明。
測試用例:
xml文件:
測試類:
/** * description:測試通過有參構造方式注入產生的循環依賴問題 * @author 70KG * @date 2018/12/21 */ public class Test02 { @Test public void m1() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test02.xml"); } }
分析上面代碼:
Spring容器創建testA的Bean實例,首先去"當前創建Bean池",查找是否當前Bean正在創建,如果沒發現,則繼續準備其需要的構造器參數testB,并將testA標識符放到"當前創建Bean池"。
Spring容器創建testB的Bean實例,首先去"當前創建Bean池",查找是否當前Bean正在創建,如果沒發現,則繼續準備其需要的構造器參數testC,并將testB標識符放到"當前創建Bean池"。
Spring容器創建testC的Bean實例,首先去"當前創建Bean池",查找是否當前Bean正在創建,如果沒發現,則繼續準備其需要的構造器參數testA,并將testC標識符放到"當前創建Bean池"。
到此為止Spring容器要去創建testA,但發現該Bean的標志符在"當前創建Bean池"中,表示了循環依賴,于是拋出BeanCurrentlyInCreationException異常。
其中"當前創建Bean池"就是一個Set集合,DefaultSingletonBeanRegistry類中beforeSingletonCreation方法,代碼如下:
protected void beforeSingletonCreation(String beanName) { if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) { throw new BeanCurrentlyInCreationException(beanName); } }
然后我們來到創建Bean實例的地方:
AbstractAutowireCapableBeanFactory類的543行,通過這個方法返回一個這個Bean的包裝對象:
--> instanceWrapper = createBeanInstance(beanName, mbd, args);----> 進入這個方法
--> AbstractAutowireCapableBeanFactory類的1129行
// Need to determine the constructor... // 需要確定構造函數,也就是說構造方法的循環依賴會在這兒return Constructor>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName); if (ctors != null || mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR || mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) { return autowireConstructor(beanName, mbd, ctors, args); } // No special handling: simply use no-arg constructor. // 無需特殊處理,僅使用無參構造即可,setter的循環依賴會在這個地方return return instantiateBean(beanName, mbd);
在上面代碼中返回Bean的包裝對象下面緊接著才是將這個對象曝光,也就是加入到SingletonFactory集合中,所以構造方法的循環引用,Spring是無法解決的,來到AbstractAutowireCapableBeanFactory的574行。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));基于setter構造的循環依賴
首先說結論:Spring是可以為我們解決這樣的依賴的,原理說白了就是用了緩存處理,也就是常說的提前曝光,為什么叫提前曝光呢?因為這個緩存中的Bean是一個還未進行賦值的Bean,僅僅是一個引用而已。
xml文件:
測試類:
/** * description:通過setter注入產生的循環依賴問題 * @author 70KG */ public class Test03 { @Test public void m1() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test03.xml"); } }
代碼分析:
Spring容器創建單例"loopA",首先根據無參構造創建Bean,并暴露到Map(singletonFactories)中,并將"loopA"標志符放到當前創建正在創建的Bean池(singletonsCurrentlyInCreation)中,然后進行setter注入"loopB"。
Spring容器創建單例"loopB",首先根據無參構造創建Bean,并暴露到Map(singletonFactories)中,并將"loopA"標志符放到當前創建正在創建的Bean池(singletonsCurrentlyInCreation)中,然后進行setter注入"loopC"。
Spring容器創建單例"loopC",首先根據無參構造創建Bean,并暴露到Map(singletonFactories)中,并將"loopA"標志符放到當前創建正在創建的Bean池(singletonsCurrentlyInCreation)中,然后進行setter注入"loopA"。在注入"loopA"的時候,由于提前暴露在singletonFactories集合中了,利用它就可以取到"loopA"正在創建的Bean對象。
最后依賴注入"testB","testA",完成setter注入。
查看控制臺輸出日志:
// 正在創建testA對象 Creating shared instance of singleton bean "testA" Creating instance of bean "testA" // 在緩存早期引用,目的是防止循環引用問題 Eagerly caching bean "testA" to allow for resolving potential circular references Creating shared instance of singleton bean "testB" Creating instance of bean "testB" Eagerly caching bean "testB" to allow for resolving potential circular references Creating shared instance of singleton bean "testC" Creating instance of bean "testC" Eagerly caching bean "testC" to allow for resolving potential circular references // 在創建testC的時候會去緩存中拿原來存儲的testA,并返回,但此時的testA是一個不完全的對象,也就是尚未初始化 Returning eagerly cached instance of singleton bean "testA" that is not fully initialized yet - a consequence of a circular reference // 緊接著完成C的創建,順便其它的也完成了 Finished creating instance of bean "testC" Finished creating instance of bean "testB" Finished creating instance of bean "testA" Returning cached instance of singleton bean "testB" Returning cached instance of singleton bean "testC"
基于setter的循環依賴利用了提前曝光機制,這一步的關鍵代碼,在AbstractAutowireCapableBeanFactory的574行,代碼如下:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
在加入SingletonFactory的前提是此Bean已經創建出來,才能夠加入到這個Map集合中,也就是提前曝光,可以讓別的Bean在初始化的時候從中拿到。否則是沒有機會加入到Map中的。
基于prototype范圍的依賴首先說結論,對于多例情況下的循環依賴,是無法解決的,因為Spring容器不進行緩存,更無法提前暴露。
測試用例:
xml文件:
測試類:
/** * description:通過setter注入產生的循環依賴問題 * @author 70KG */ public class Test03 { @Test public void m1() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("test03.xml"); LoopA loopA = context.getBean(LoopA.class); System.out.println(loopA); } }
會拋出BeanCurrentlyInCreationException異常。
Spring是如何檢測循環依賴來到AbstractBeanFactory的246行,代碼如下:
Object sharedInstance = getSingleton(beanName);
這一步是從緩存中獲取以前創建的實例,如果發現存在,那么就存在循環依賴。
到此,全文完,自我感覺比其他的整理還算詳細,如有疑問,請留言。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/73177.html
摘要:實例化時,發現又依賴于。一些緩存的介紹在進行源碼分析前,我們先來看一組緩存的定義。可是看完源碼后,我們似乎仍然不知道這些源碼是如何解決循環依賴問題的。 1. 簡介 本文,我們來看一下 Spring 是如何解決循環依賴問題的。在本篇文章中,我會首先向大家介紹一下什么是循環依賴。然后,進入源碼分析階段。為了更好的說明 Spring 解決循環依賴的辦法,我將會從獲取 bean 的方法getB...
摘要:簡介為了寫容器源碼分析系列的文章,我特地寫了一篇容器的導讀文章。在做完必要的準備工作后,從本文開始,正式開始進入源碼分析的階段。從緩存中獲取單例。返回以上就是和兩個方法的分析。 1. 簡介 為了寫 Spring IOC 容器源碼分析系列的文章,我特地寫了一篇 Spring IOC 容器的導讀文章。在導讀一文中,我介紹了 Spring 的一些特性以及閱讀 Spring 源碼的一些建議。在...
摘要:本文是容器源碼分析系列文章的第一篇文章,將會著重介紹的一些使用方法和特性,為后續的源碼分析文章做鋪墊。我們可以通過這兩個別名獲取到這個實例,比如下面的測試代碼測試結果如下本小節,我們來了解一下這個特性。 1. 簡介 Spring 是一個輕量級的企業級應用開發框架,于 2004 年由 Rod Johnson 發布了 1.0 版本。經過十幾年的迭代,現在的 Spring 框架已經非常成熟了...
摘要:關于創建實例的過程,我將會分幾篇文章進行分析。源碼分析創建實例的入口在正式分析方法前,我們先來看看方法是在哪里被調用的。時,表明方法不存在,此時拋出異常。該變量用于表示是否提前暴露單例,用于解決循環依賴。 1. 簡介 在上一篇文章中,我比較詳細的分析了獲取 bean 的方法,也就是getBean(String)的實現邏輯。對于已實例化好的單例 bean,getBean(String) ...
摘要:如下圖注意,這里不是函數的循環調用,是對象的相互依賴關系。因此如果在創建過程中發現自己已經在當前創建池里時將拋出異常表示循環依賴而對于創建完畢的將從當前創建池中清除掉。 showImg(https://segmentfault.com/img/bVbs5kw?w=339&h=193); 什么是循環依賴? 循環依賴其實就是循環引用,也就是兩個或則兩個以上的bean互相持有對方,最終形成閉...
閱讀 929·2021-11-22 12:09
閱讀 3704·2021-09-27 13:36
閱讀 1390·2021-08-20 09:37
閱讀 4007·2019-12-27 12:22
閱讀 2353·2019-08-30 15:55
閱讀 2358·2019-08-30 13:16
閱讀 2817·2019-08-26 17:06
閱讀 3433·2019-08-23 18:32