Spring 是如何解決循環依賴的?
循環依賴:
Spring 循環依賴有三種情況:
- 構造器的循環依賴,這種依賴 Spring 無法處理,直接抛出 BeanCurrentlyInCreationException 異常
- 單例模式下的 setter 循環依賴,可以通過三級緩存處理
- 非單例循環依賴,無法處理,BeanCurrentlyInCreationException 異常
構造器循環依賴
正要建立的 bean 記錄在緩存中,Spring 容器架構一個正在建立的 bean 辨別符放在一個 “目前建立 bean 池”中國, 是以如果在建立 Bean 過程中,如果發現已經在目前建立的 Bean 池中,則抛出 BeanCurrentlyInCreationException 異常表示循環依賴,對于建立完畢的 Bean 将從“目前建立 Bean 池”中清除。 先看個例子:
xml 配置
測試代碼
報錯如下:
Setter 注入(單例)
測試代碼
運作時不會報錯的.
Setter 注入(非單例模式)
對于 prototype 作用域的 Bean ,Spring 容器無法完成依賴注入,因為 Prototype 作用域的bean ,sring 不進行緩沖,無法提提前暴露一個建立中的Bean。會抛出異常。
測試代碼
報錯
Spring Bean 建立過程
- 執行個體化 Bean 對象,createBeanInstance 執行個體化
- 設定 Bean 屬性,populateBean 填充屬性
- 通過 各種 Aware 接口聲明了依賴關系,則會注入 Bean 對容器基礎設施層面的依賴,包括 BeanNameAware、BeanFactoryAware 和 ApplicationContextAware 分别注入 BeanID, BeanFactory 或者 ApplicationContext。
- 調用 BeanPostProcessor 的前置初始化方法 postProcessBeforeInitialization
- 如果實作了 InitializingBean 接口,會調用 afterProperties 方法。
- 調用 Bean 自定義的 init 方法,initializeBean 調用 xml 的 init方法
- 調用 BeanPostprocessor 的字尾初始方法 postProcessAfterInitialization。
- 建立過程完畢。
Spring 是如何解決單例的循環依賴問題的呢?
Spring 采用的三級緩存解決了單例的循環依賴問題。
三級緩存:
Spring 源碼 DefaultSingletonBeanRegistry.java 中:
To be called for eager registration of singletons, e.g. to be able to
* resolve circular references.
* @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object
*/
protected void addSingletonFactory(String beanName, ObjectFactory singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
// 一級緩存執行個體化 bean 中不包含 正建立的 beanif (!this.singletonObjects.containsKey(beanName)) {
// 三級緩存中添加
this.singletonFactories.put(beanName, singletonFactory);
// 二級緩沖删除
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
AbstractBeanFactory.doGetBean()
getSingleton 可以這樣了解:
- 先從一級緩沖中看有沒有建立好的 bean ,有就直接傳回。
- 如果沒有,那麼從二級緩存中看有沒有建立 半成品的 Bean,如果有,直接傳回
- 如果沒有,從三級緩存中看下有沒有建立過程中的 bean,還沒有 那麼通過 singletonFactory.getObject 最後到 createBean 建立。
Spring 是怎麼解決單例setter注入循環依賴的?
- Spring是通過遞歸的方式擷取目标bean及其所依賴的bean的;
- Spring執行個體化一個bean的時候,是分兩步進行的,首先執行個體化目标bean,然後為其注入屬性
setter 注入是屬性注入和構造器注入不一樣,spring初始化是先建立bean ,然後注入屬性的。單例 的setter采用三級緩存各自拿到各自的屬性引用,然後再屬性注入,最後各自完成執行個體化,不存在循環等待死鎖的問題。
場景:A 依賴 B,B 依賴 A。
假設建立 A 對象的時候進入 getSingleton 方法。建立 B 的時候進入了個 doCreateBean 方法,在建立 B 還沒建立完過程中,會在三級緩存 singletonFactories 先放一個 B,此時,如果建立 A 對象時,一級緩存沒有B,從二級緩存找,二級緩存沒有,從三級别緩存中找到就可以直接傳回,并将自身A放入一級緩存中。
此時 B 在初始化過程中,從一級緩存中取到了A,這樣B就拿到了A的引用,這樣也B也就在拿到A的過程中完成了初始化。