面試題:如何解決Spring 的循環(huán)依賴問題

Spring 的循環(huán)依賴問題

什么是循環(huán)依賴

什么是循環(huán)依賴呢?可以把它拆分成循環(huán)和依賴兩個(gè)部分來(lái)看,循環(huán)是指計(jì)算機(jī)領(lǐng)域中的循環(huán),執(zhí)行流程形成閉合回路;依賴就是完成這個(gè)動(dòng)作的前提準(zhǔn)備條件,和我們平常說的依賴大體上含義一致。放到 Spring 中來(lái)看就一個(gè)或多個(gè) Bean 實(shí)例之間存在直接或間接的依賴關(guān)系,構(gòu)成循環(huán)調(diào)用,循環(huán)依賴可以分為直接循環(huán)依賴和間接循環(huán)依賴,直接循環(huán)依賴的簡(jiǎn)單依賴場(chǎng)景:Bean A 依賴于 Bean B,然后 Bean B 又反過來(lái)依賴于 Bean A(Bean A -> Bean B -> Bean A),間接循環(huán)依賴的一個(gè)依賴場(chǎng)景:Bean A 依賴于 Bean B,Bean B 依賴于 Bean C,Bean C 依賴于 Bean A,中間多了一層,但是最終還是形成循環(huán)(Bean A -> Bean B -> Bean C -> Bean A)。

循環(huán)依賴的類型

第一種是自依賴,自己依賴自己從而形成循環(huán)依賴,一般情況下不會(huì)發(fā)生這種循環(huán)依賴,因?yàn)樗苋菀妆晃覀儼l(fā)現(xiàn)。

1.png

第二種是直接依賴,發(fā)生在兩個(gè)對(duì)象之間,比如:Bean A 依賴于 Bean B,然后 Bean B 又反過來(lái)依賴于 Bean A,如果比較細(xì)心的話肉眼也不難發(fā)現(xiàn)。

2.png

第三種是間接依賴,這種依賴類型發(fā)生在 3 個(gè)或者以上的對(duì)象依賴的場(chǎng)景,間接依賴最簡(jiǎn)單的場(chǎng)景:Bean A 依賴于 Bean B,Bean B 依賴于 Bean C,Bean C 依賴于 Bean A,可以想象當(dāng)中間依賴的對(duì)象很多時(shí),是很難發(fā)現(xiàn)這種循環(huán)依賴的,一般都是借助一些工具排查。

3.png

Spring 對(duì)幾種循環(huán)依賴場(chǎng)景支持情況

在介紹 Spring 對(duì)幾種循環(huán)依賴場(chǎng)景的處理方式之前,先來(lái)看看在 Spring 中循環(huán)依賴會(huì)有哪些場(chǎng)景,大部分常見的場(chǎng)景總結(jié)如下圖所示:

4.png

有句話說得好,源碼之下無(wú)秘密,下面就通過源碼探究這些場(chǎng)景 Spring 是否支持,以及支持的原因或者不支持的原因,話不多說,下面進(jìn)入正題。

第 ① 種場(chǎng)景——單例 Bean 的 setter 注入

這種使用方式也是最常用的方式之一,假設(shè)有兩個(gè) Service 分別為 OrderService(訂單相關(guān)業(yè)務(wù)邏輯)和 TradeService(交易相關(guān)業(yè)務(wù)邏輯),代碼如下:

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
public class OrderService {

  @Autowired
  private TradeService tradeService;

  public void testCreateOrder() {
    // omit business logic ...
  }

}

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
public class TradeService {

  @Autowired
  private OrderService orderService;

  public void testCreateTrade() { 
    // omit business logic ...
   }

}

這種循環(huán)依賴場(chǎng)景,程序是可以正常運(yùn)行的,從代碼上看確實(shí)是有循環(huán)依賴了,也就是說 Spring 是支持這種循環(huán)依賴場(chǎng)景的,這里我們察覺不到循環(huán)依賴的原因是 Spring 已經(jīng)默默地解決了。

假設(shè)沒有做任何處理,按照正常的創(chuàng)建邏輯來(lái)執(zhí)行的話,流程是這樣的:容器先創(chuàng)建 OrderService,發(fā)現(xiàn)依賴于 TradeService,再創(chuàng)建 OrderService,又發(fā)現(xiàn)依賴于 TradeService ... ,發(fā)生無(wú)限死循環(huán),最后發(fā)生棧溢出錯(cuò)誤,程序停止。為了支持這種常見的循環(huán)依賴場(chǎng)景,Spring 將創(chuàng)建對(duì)象分為如下幾個(gè)步驟:

  1. 實(shí)例化一個(gè)新對(duì)象(在堆中),但此時(shí)尚未給對(duì)象屬性賦值
  2. 給對(duì)象賦值
  3. 調(diào)用 BeanPostProcessor 的一些實(shí)現(xiàn)類的方法,在這個(gè)階段,Bean 已經(jīng)創(chuàng)建并賦值屬性完成。這時(shí)候容器中所有實(shí)現(xiàn) BeanPostProcessor 接口的類都會(huì)被調(diào)用(e.g. AOP)
  4. 初始化(如果實(shí)現(xiàn)了 InitializingBean,就會(huì)調(diào)用這個(gè)類的方法來(lái)完成類的初始化)
  5. 返回創(chuàng)建出來(lái)的實(shí)例

為此,Spring 引入了三級(jí)緩存來(lái)處理這個(gè)問題(三級(jí)緩存定義在 org.springframework.beans.factory.support.DefaultSingletonBeanRegistry 中),第一級(jí)緩存 singletonObjects 用于存放完全初始化好的 Bean,從該緩存中取出的 Bean 可以直接使用,第二級(jí)緩存 earlySingletonObjects 用于存放提前暴露的單例對(duì)象的緩存,存放原始的 Bean 對(duì)象(屬性尚未賦值),用于解決循環(huán)依賴,第三級(jí)緩存 singletonFactories 用于存放單例對(duì)象工廠的緩存,存放 Bean 工廠對(duì)象,用于解決循環(huán)依賴。上述實(shí)例使用三級(jí)緩存的處理流程如下所示:

5.png

如果你看過三級(jí)緩存的定義源碼的話,可能也有這樣的疑問:為什么第三級(jí)的緩存的要定義成 Map<String, ObjectFactory<?>>,不能直接緩存對(duì)象嗎?這里不能直接保存對(duì)象實(shí)例,因?yàn)檫@樣就無(wú)法對(duì)其做增強(qiáng)處理了。詳情可見類 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 方法部分源碼如下:

6.png

第 ② 種場(chǎng)景——多例 Bean 的 setter 注入

這種方式平常使用得相對(duì)較少,還是使用前文的兩個(gè) Service 作為示例,唯一不同的地方是現(xiàn)在都聲明為多例了,示例代碼如下:

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class OrderService {

  @Autowired
  private TradeService tradeService;

  public void testCreateOrder() {
    // omit business logic ...
  }

}

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class TradeService {

  @Autowired
  private OrderService orderService;

  public void testCreateTrade() { 
    // omit business logic ...
   }

}

如果你在 Spring 中運(yùn)行以上代碼,是可以正常啟動(dòng)成功的,原因是在類 org.springframework.beans.factory.support.DefaultListableBeanFactory 的 preInstantiateSingletons() 方法預(yù)實(shí)例化處理時(shí),過濾掉了多例類型的 Bean,方法部分代碼如下:

7.png

但是如果此時(shí)有其它單例類型的 Bean 依賴到這些多例類型的 Bean 的時(shí)候,就會(huì)報(bào)如下所示的循環(huán)依賴錯(cuò)誤了。

8.png

第 ③ 種場(chǎng)景——代理對(duì)象的 setter 注入

這種場(chǎng)景也會(huì)經(jīng)常碰到,有時(shí)候?yàn)榱藢?shí)現(xiàn)異步調(diào)用會(huì)在 XXXXService 類的方法上添加 @Async 注解,讓方法對(duì)外部變成異步調(diào)用(前提要是要在啟用類上添加啟用注解哦 @EnableAsync),示例代碼如下:

/**
 * @author mghio
 * @since 2021-07-17
 */
@EnableAsync
@SpringBootApplication
public class BlogMghioCodeApplication {

  public static void main(String[] args) {
    SpringApplication.run(BlogMghioCodeApplication.class, args);
  }

}

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class OrderService {

  @Autowired
  private TradeService tradeService;

  @Async
  public void testCreateOrder() {
    // omit business logic ...
  }

}

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class TradeService {

  @Autowired
  private OrderService orderService;

  public void testCreateTrade() { 
    // omit business logic ...
   }

}

在標(biāo)有 @Async 注解的場(chǎng)景下,在添加啟用異步注解(@EnableAsync)后,代理對(duì)象會(huì)通過 AOP 自動(dòng)生成。以上代碼運(yùn)行會(huì)拋出 BeanCurrentlyInCreationException 異常。運(yùn)行的大致流程如下圖所示:

9.png

源碼在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory 類的方法 doCreateBean 中,會(huì)判斷第二級(jí)緩存 earlySingletonObjects 中的對(duì)象是否等于原始對(duì)象,方法判斷部分的源碼如下:

10.png

二級(jí)緩存存放的對(duì)象是 AOP 生成出來(lái)的代理對(duì)象,和原始對(duì)象不相等,所以拋出了循環(huán)依賴錯(cuò)誤。如果細(xì)看源碼的話,會(huì)發(fā)現(xiàn)如果二級(jí)緩存是空的話會(huì)直接返回(因?yàn)楸容^的對(duì)象都沒有,根本無(wú)法校驗(yàn)了),就不會(huì)報(bào)循環(huán)依賴的錯(cuò)誤了,默認(rèn)情況下,Spring 是按照文件全路徑遞歸搜索,按路徑 + 文件名 排序,排序靠前先加載,所以我們只要調(diào)整這兩個(gè)類名稱,讓方法標(biāo)有 @Async 注解的類排序在后面即可。

第 ④ 種場(chǎng)景——構(gòu)造器注入

構(gòu)造器注入的場(chǎng)景很少,到目前為止我所接觸過的公司項(xiàng)目和開源項(xiàng)目中還沒遇到使用構(gòu)造器注入的,雖然用得不多,但是需要知道 Spring 為什么不支持這種場(chǎng)景的循環(huán)依賴,構(gòu)造器注入的示例代碼如下:

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
public class OrderService {

  private TradeService tradeService;

  public OrderService(TradeService tradeService) {
    this.tradeService = tradeService;
  }

  public void testCreateOrder() {
    // omit business logic ...
  }

}

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
public class TradeService {

  private OrderService orderService;

  public TradeService(OrderService orderService) {
    this.orderService = orderService;
  }

  public void testCreateTrade() {
    // omit business logic ...
  }

}

構(gòu)造器注入無(wú)法加入到第三級(jí)緩存當(dāng)中,Spring 框架中的三級(jí)緩存在此場(chǎng)景下無(wú)用武之地,所以只能拋出異常,整體流程如下(虛線表示無(wú)法執(zhí)行,為了直觀也把下一步畫出來(lái)了):

11.png

第 ⑤ 種場(chǎng)景——DependsOn 循環(huán)依賴

這種 DependsOn 循環(huán)依賴場(chǎng)景很少,一般情況下不怎么使用,了解一下會(huì)導(dǎo)致循環(huán)依賴的問題即可,@DependsOn 注解主要是用來(lái)指定實(shí)例化順序的,示例代碼如下:

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
@DependsOn("tradeService")
public class OrderService {

  @Autowired
  private TradeService tradeService;

  public void testCreateOrder() {
    // omit business logic ...
  }

}

/**
 * @author mghio
 * @since 2021-07-17
 */
@Service
@DependsOn("orderService")
public class TradeService {

  @Autowired
  private OrderService orderService;

  public void testCreateTrade() {
    // omit business logic ...
  }

}

通過上文,我們知道,如果這里的類沒有標(biāo)注 @DependsOn 注解的話是可以正常運(yùn)行的,因?yàn)?Spring 支持單例 setter 注入,但是加了示例代碼的 @DependsOn 注解后會(huì)報(bào)循環(huán)依賴錯(cuò)誤,原因是在類 org.springframework.beans.factory.support.AbstractBeanFactory 的方法 doGetBean() 中檢查了 dependsOn 的實(shí)例是否有循環(huán)依賴,如果有循環(huán)依賴則拋出循環(huán)依賴異常,方法判斷部分代碼如下:

12.png

總結(jié)

本文主要介紹了什么是循環(huán)依賴以及 Spring 對(duì)各種循環(huán)依賴場(chǎng)景的處理,文中只列出了部分涉及到的源碼,都標(biāo)了所在源碼中的位置,感興趣的朋友可以去看看完整源碼,最后 Spring 對(duì)各種循環(huán)依賴場(chǎng)景的支持情況如下圖所示(P.S. Spring 版本:5.1.9.RELEASE):


13.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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