Spring 的循環(huán)依賴問(wèn)題
什么是循環(huán)依賴
什么是循環(huán)依賴呢挟秤?可以把它拆分成循環(huán)和依賴兩個(gè)部分來(lái)看凛澎,循環(huán)是指計(jì)算機(jī)領(lǐng)域中的循環(huán)双妨,執(zhí)行流程形成閉合回路隐锭;依賴就是完成這個(gè)動(dòng)作的前提準(zhǔn)備條件觉阅,和我們平常說(shuō)的依賴大體上含義一致缔恳。放到 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 又反過(guò)來(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)乞巧。
第二種是直接依賴涨椒,發(fā)生在兩個(gè)對(duì)象之間,比如:Bean A 依賴于 Bean B绽媒,然后 Bean B 又反過(guò)來(lái)依賴于 Bean A蚕冬,如果比較細(xì)心的話肉眼也不難發(fā)現(xiàn)。
第三種是間接依賴是辕,這種依賴類型發(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)依賴的,一般都是借助一些工具排查牌芋。
Spring 對(duì)幾種循環(huán)依賴場(chǎng)景支持情況
在介紹 Spring 對(duì)幾種循環(huán)依賴場(chǎng)景的處理方式之前蚓炬,先來(lái)看看在 Spring 中循環(huán)依賴會(huì)有哪些場(chǎng)景松逊,大部分常見的場(chǎng)景總結(jié)如下圖所示:
有句話說(shuō)得好躺屁,源碼之下無(wú)秘密,下面就通過(guò)源碼探究這些場(chǎng)景 Spring 是否支持经宏,以及支持的原因或者不支持的原因犀暑,話不多說(shuō),下面進(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)依賴了抽碌,也就是說(shuō) 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è)步驟:
- 實(shí)例化一個(gè)新對(duì)象(在堆中),但此時(shí)尚未給對(duì)象屬性賦值
- 給對(duì)象賦值
- 調(diào)用 BeanPostProcessor 的一些實(shí)現(xiàn)類的方法盔几,在這個(gè)階段晴弃,Bean 已經(jīng)創(chuàng)建并賦值屬性完成。這時(shí)候容器中所有實(shí)現(xiàn) BeanPostProcessor 接口的類都會(huì)被調(diào)用(e.g. AOP)
- 初始化(如果實(shí)現(xiàn)了 InitializingBean问欠,就會(huì)調(diào)用這個(gè)類的方法來(lái)完成類的初始化)
- 返回創(chuàng)建出來(lái)的實(shí)例
為此肝匆,Spring 引入了三級(jí)緩存來(lái)處理這個(gè)問(wèn)題(三級(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í)緩存的處理流程如下所示:
如果你看過(guò)三級(jí)緩存的定義源碼的話蕊程,可能也有這樣的疑問(wèn):為什么第三級(jí)的緩存的要定義成 Map<String, ObjectFactory<?>>,不能直接緩存對(duì)象嗎驼唱?這里不能直接保存對(duì)象實(shí)例藻茂,因?yàn)檫@樣就無(wú)法對(duì)其做增強(qiáng)處理了。詳情可見類 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean 方法部分源碼如下:
第 ② 種場(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í),過(guò)濾掉了多例類型的 Bean惭婿,方法部分代碼如下:
但是如果此時(shí)有其它單例類型的 Bean 依賴到這些多例類型的 Bean 的時(shí)候不恭,就會(huì)報(bào)如下所示的循環(huán)依賴錯(cuò)誤了。
第 ③ 種場(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ì)通過(guò) AOP 自動(dòng)生成。以上代碼運(yùn)行會(huì)拋出 BeanCurrentlyInCreationException 異常打颤。運(yùn)行的大致流程如下圖所示:
源碼在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory 類的方法 doCreateBean 中暴拄,會(huì)判斷第二級(jí)緩存 earlySingletonObjects 中的對(duì)象是否等于原始對(duì)象,方法判斷部分的源碼如下:
二級(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)景很少怒坯,到目前為止我所接觸過(guò)的公司項(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)了):
第 ⑤ 種場(chǎng)景——DependsOn 循環(huán)依賴
這種 DependsOn 循環(huán)依賴場(chǎng)景很少,一般情況下不怎么使用弄慰,了解一下會(huì)導(dǎo)致循環(huán)依賴的問(wè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 ...
}
}
通過(guò)上文陆爽,我們知道,如果這里的類沒有標(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)依賴異常驴剔,方法判斷部分代碼如下:
總結(jié)
本文主要介紹了什么是循環(huán)依賴以及 Spring 對(duì)各種循環(huán)依賴場(chǎng)景的處理,文中只列出了部分涉及到的源碼粥庄,都標(biāo)了所在源碼中的位置丧失,感興趣的朋友可以去看看完整源碼,最后 Spring 對(duì)各種循環(huán)依賴場(chǎng)景的支持情況如下圖所示(P.S. Spring 版本:5.1.9.RELEASE):