在使用spring框架開發時,會出現類A 依賴 類B ,類B 又依賴 類A的情況,就是循環依賴了,那么spring容器是怎么處理的呢
在看循環依賴之前我們先來看一下spring中的三級緩存。
一:spring容器中的緩存
spring容器對對象的注冊維護,主要是通過DefaultSingletonBeanRegistry來實現的,這個類提供了一些存儲以及獲取的方法,我們首先類分析一下這個類
下面是三級緩存集合:
下面看一下這個類里基本的方法:
注冊單例bean:
放入一級緩存中:
放入三級緩存中:
獲取單例對象的過程:
獲取單例bean的核心邏輯:
二:spring循環依賴的源碼分析
1:單例bean屬性的循環依賴
准備工作:創建TestA類,依賴TestB類,然后TestB類 又反過來依賴 TestA 類
@Component public class TestA { @Autowired private TestB testB; }
@Component public class TestB { @Autowired private TestA testA; }
我們來分析一下這種屬性循環依賴的情況
我們從AbstractBeanFactory類的doGetBean看起
如果緩存中不存在,那么就調用getSingleton實例化TestA對象:
在堆內存中開辟空間,創建對象后,如果單例bean,運行循環依賴,那么放入三級緩存中。
設置屬性:
設置屬性testB
具體Autowired的過程這里不再分析,可以看上面章節分析Autowired注入的過程,在AutowiredAnnotationBeanPostProcessor方法中會涉及到getBean(testB),然后又會觸發
testB實例化的過程,
遞歸回到最開始testA的實例化過程,流程和之前實例化testA一樣,最后也是走到設置屬性:
testB實例化過程中,設置屬性testA
又會走到getBean實例化testA,這時又遞歸回到了DefaultListableBeanFactory的getBean方法
然后返回一個沒有初始化的testA對象,然后testB設置屬性成功,初始化完成,緩存並返回對象,
又回到了testA對象設置屬性的時候,然后testA設置屬性完成,到這里單例的循環依賴就成功的解決了。
測試代碼:
運行結果:
通過結果可以看出spring是支持單例的屬性的循環依賴的
2:單例bean 構造器的循環依賴
准備工作:
@Component public class TestA { private TestB testB; @Autowired public TestA(TestB testB){ this.testB = testB; } }
@Component public class TestB { private TestA testA; @Autowired public TestB(TestA testA){ this.testA = testA; } }
運行一下:
拋出異常:從運行結果可以看出,首先實例化testA,創建testA實例的時候,發現是有參數構造函數,所以尋找參數testB,然后getBean,實例化testB,
發現testB也是有參數構造函數,所以尋找參數testA,getBean(testA),由於testA還沒有創建,所以沒法放入集合中標記,拋出異常。
總結:spring不支持構造器的循環依賴
3:prototype類型非單例循環依賴
doGetBean方法中:
准備工作:
@Component @Scope("prototype") public class TestA { @Autowired private TestB testB; }
@Component @Scope("prototype") public class TestB { @Autowired private TestA testA; }
運行測試用例:
從運行結果可以看出,spring容器不支持prototype類型的循環依賴。
4:如果相互依賴的兩個類,一個類TestA是非單例,另一個類TestB單例,會怎樣?
@Component @Scope("prototype") public class TestA { @Autowired private TestB testB; }
@Component //@Scope("prototype") public class TestB { @Autowired private TestA testA; }
運行結果:
正常運行,沒有拋出異常。
因為spring預實例化只會實例化非單例的bean,那么TestB會先實例化,設置屬性時發現依賴TestA ,然后實例化TestA,
TestA是非單例,創建bean,設置屬性時,發現依賴TestB,getBean(testB),實例化TestB,這時緩存中已經存在testB,從
三級緩存中拿到testB,TestA實例化完成,返回對象,TestB實例化完成。