一、什么是循環依賴
循環依賴即循環引用,形成閉環。比如,A 依賴 B,B 又依賴 A,形成了循環依賴;或者 A 依賴 B,B 依賴 C,C 又依賴 A,形成了循環依賴;更或者是自己依賴自己。如圖:這不是函數的循環調用,是對象的相互依賴關系。循環調用其實就是一個死循環,除非有終結條件。Spring 循環依賴場景有:
- 構造器的循環依賴
- 構造器依賴 + field/setter 依賴(A 的構造器依賴 B,B 的 field/setter 依賴 A)
- field 依賴或者 setter 依賴
具體情況如下:
1??A 的構造方法中依賴 B 的實例對象,同時 B 的構造方法中依賴 A 的實例對象。(無法解決)
2??A 的構造方法中依賴 B 的實例對象,同時 B 的某個 field/setter 需要 A 的實例對象,以及反之。(可以解決)
3??A 的某個 field/setter 依賴 B 的實例對象,同時 B 的某個 field/setter 依賴 A 的實例對象,以及反之。(可以解決)
二、Spring 三大循環依賴
1??構造器注入循環依賴【不能解決】
表示通過構造器注入構成的循環依賴,此依賴是無法解決的,只能拋出BeanCurrentlyIn CreationException表示循環依賴。
@Service
public class A {
public A(B b) {
}
}
@Service
public class B {
public B(A a) {
}
}
Spring 容器會將每一個正在創建的 Bean 標識符放在一個“當前創建 Bean 池”中,Bean 標識符在創建過程中將一直保持在這個池中,因此如果在創建 Bean 過程中發現自己已經在“當前創建 Bean 池”里時將拋出BeanCurrentlyInCreationException表示循環依賴;而對于創建完畢的 Bean 將從“當前創建 Bean 池”中清除掉。執行結果報錯信息為:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
根本原因:Spring 解決循環依賴依靠的是 Bean 的“中間態”這個概念,而這個中間態指的是已經實例化,但還沒初始化的狀態。而構造器表示已經完成實例化,所以構造器的循環依賴無法解決。
2??singleton 模式 field 屬性注入循環依賴【可以解決】
@Service
public class A {
@Autowired
private B b;
}
@Service
public class B {
@Autowired
private A a;
}
結果:項目啟動成功,能夠正常work
備注:setter 方法注入方式原理同字段注入方式類似。
3??prototype 模式 field 屬性注入循環依賴【不能解決】
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class A {
@Autowired
private B b;
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class B {
@Autowired
private A a;
}
對于 prototype 作用域 的 bean,Spring 容器無法完成依賴注入,因為 Spring 容器不進行緩存 prototype 作用域的 bean,因此無法提前暴露一個創建中的 bean。
scope="prototype"
意思是每次請求都會創建一個實例對象。兩者的區別是:有狀態的 bean 都使用 Prototype 作用域,無狀態的一般都使用 singleton 單例作用域。
因此本例中啟動時是不會報錯的(因為非單例 Bean 默認不會初始化,而是使用時才會初始化),所以需要手動 getBean() 或者在一個單例 Bean 內 @Autowired 一下它即可:
// 在單例Bean內注入
@Autowired
private A a;
打印結果:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
三、怎么檢測是否存在循環依賴
檢測循環依賴相對比較容易,Bean 在創建的時候可以給該 Bean 打標,如果遞歸調用回來發現正在創建中的話,說明循環依賴了。
四、Spring 是如果解決循環依賴的
Spring 通過三級緩存加上“提前曝光”機制,配合 Java 的對象引用原理,比較完美地解決了某些情況下的循環依賴問題。
- A 首先完成了初始化的第一步,并且將自己提前曝光到 singletonFactories 中。此時進行初始化的第二步,發現自己依賴對象 B,就嘗試去 get(B),發現 B 還沒有被 create,所以走 create 流程。
- B 在初始化第一步的時候發現自己依賴了對象 A,于是嘗試 get(A),由于 A 通過 ObjectFactory 將自己提前曝光了,所以 B 能夠通過 ObjectFactory.getObject 拿到 A 對象。
- B 拿到 A 對象后順利完成了初始化階段 1、2、3,完全初始化之后將自己放入到一級緩存 singletonObjects 中。此時返回 A 中,A 拿到 B 的對象順利完成自己的初始化階段 2、3,最終 A 也完成了初始化。