Spring 循環(huán)依賴(circular dependency)

一、什么是循環(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)依賴場景有:

  1. 構(gòu)造器的循環(huán)依賴
  2. 構(gòu)造器依賴 + field/setter 依賴(A 的構(gòu)造器依賴 B,B 的 field/setter 依賴 A)
  3. 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)依賴問題。

  1. A 首先完成了初始化的第一步,并且將自己提前曝光到 singletonFactories 中。此時進(jìn)行初始化的第二步,發(fā)現(xiàn)自己依賴對象 B,就嘗試去 get(B),發(fā)現(xiàn) B 還沒有被 create,所以走 create 流程。
  2. B 在初始化第一步的時候發(fā)現(xiàn)自己依賴了對象 A,于是嘗試 get(A),由于 A 通過 ObjectFactory 將自己提前曝光了,所以 B 能夠通過 ObjectFactory.getObject 拿到 A 對象。
  3. B 拿到 A 對象后順利完成了初始化階段 1、2、3,完全初始化之后將自己放入到一級緩存 singletonObjects 中。此時返回 A 中,A 拿到 B 的對象順利完成自己的初始化階段 2、3,最終 A 也完成了初始化。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容