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