Spring 依賴注入與循環(huán)依賴總結(jié)
1. Spring 三級緩存解決循環(huán)依賴
? 適用場景:字段注入(@Autowired 直接加在字段上)或 Setter 注入(@Autowired 加在 Setter 方法上)。
? 解決流程:
- 一級緩存(
singletonObjects):存放完全初始化好的單例 Bean。 - 二級緩存(
earlySingletonObjects):存放提前暴露的半成品 Bean(已實(shí)例化但未初始化)。 - 三級緩存(
singletonFactories):存放生成 Bean 的ObjectFactory(用于延遲創(chuàng)建代理對象)。 ?? 關(guān)鍵點(diǎn):Spring 先創(chuàng)建 Bean 的實(shí)例(調(diào)用無參構(gòu)造器),再通過緩存逐步解決依賴,避免死鎖。
舉例:
個(gè)人理解就是,以UserService為例,先創(chuàng)建UserService單例實(shí)例放在一級緩存中,如果檢測到如@Transition等注解,將單例實(shí)例包裝成工廠存到三級緩存中,到UserService初始化時(shí)發(fā)現(xiàn)自注入,分別從一二三級緩存中查找(告訴我查找什么),發(fā)現(xiàn)三級緩存中包含UserService 的工廠,然后調(diào)用改工廠生成代理對象,并且該代理對象中有對UserService為原始對象的引用,而不是對代理對象的引用,然后生成代理對象存到二級緩存中,并注入到原始對象自注入的UserService中(此時(shí)原始對象不是有對代理對象的引用嗎?),然后繼續(xù)完成初始化如@PostConstruct,然后將代理對象放到一級緩存中(此時(shí)原始對象還在嗎?),最后清楚二級三級緩存中的UserService代理對象和工廠。
你的理解基本正確,但有些細(xì)節(jié)需要澄清和補(bǔ)充。讓我們以 UserService 為例,梳理 Spring 解決循環(huán)依賴(自注入)的完整流程,并明確每一步緩存的狀態(tài)和對象引用關(guān)系。
1. 創(chuàng)建UserService原始對象
- 動作:Spring 調(diào)用
UserService的構(gòu)造函數(shù),創(chuàng)建一個(gè)原始對象(非代理)。 - 緩存狀態(tài):
- 一級緩存(單例池):無
UserService。 - 二級緩存(早期暴露對象):無
UserService。 - 三級緩存(對象工廠):無
UserService。
- 一級緩存(單例池):無
2. 將原始對象包裝成工廠,存入三級緩存
- 動作:
- 檢測到
UserService需要代理(如被@Transactional標(biāo)記),Spring 將原始對象包裝成一個(gè)ObjectFactory工廠,存入三級緩存。 - 此時(shí)對象尚未初始化(未填充屬性、未執(zhí)行
@PostConstruct)。
- 檢測到
- 緩存狀態(tài):
- 一級緩存:無
UserService。 - 二級緩存:無
UserService。 - 三級緩存:
UserService的ObjectFactory。
- 一級緩存:無
3. 處理UserService的自注入
- 動作:
- Spring 開始初始化
UserService,發(fā)現(xiàn)它依賴自身(如通過@Autowired自注入)。 - 依次從一級、二級、三級緩存查找:
- 一級緩存:未找到(因?yàn)?nbsp;
UserService未完全初始化)。 - 二級緩存:未找到。
- 三級緩存:找到
UserService的ObjectFactory,調(diào)用它生成對象。
- 一級緩存:未找到(因?yàn)?nbsp;
- Spring 開始初始化
- 生成代理對象:
- 工廠調(diào)用會創(chuàng)建
UserService的代理對象(如 JDK 動態(tài)代理或 CGLIB 代理)。 - 關(guān)鍵點(diǎn):代理對象內(nèi)部持有對原始對象的引用(即
target屬性指向原始對象)。
- 工廠調(diào)用會創(chuàng)建
- 緩存更新:
- 將代理對象存入二級緩存(此時(shí)原始對象仍在創(chuàng)建中)。
- 三級緩存中的工廠不會被移除(直到
UserService完全初始化)。
- 注入依賴:
- 將代理對象注入到原始對象的自依賴字段中(此時(shí)原始對象持有代理對象的引用)。
4. 繼續(xù)初始化原始對象
- 動作:
- 完成屬性填充(如其他依賴注入)。
- 執(zhí)行
@PostConstruct初始化方法。 - 此時(shí)原始對象已完全初始化,但仍是原始對象(未被代理替換)。
- 緩存狀態(tài):
- 一級緩存:無
UserService(因?yàn)樯形捶湃耄?/li> - 二級緩存:
UserService的代理對象。 - 三級緩存:
UserService的ObjectFactory。
- 一級緩存:無
5. 將代理對象提升到一級緩存
- 動作:
- Spring 將二級緩存中的代理對象移動到一級緩存(單例池)。
- 清除二級緩存和三級緩存中的
UserService相關(guān)條目。
- 緩存狀態(tài):
- 一級緩存:
UserService的代理對象。 - 二級緩存:無
UserService。 - 三級緩存:無
UserService。
- 一級緩存:
關(guān)鍵問題解答
- 原始對象的去向:
- 原始對象被代理對象持有(通過
target屬性引用),但對外暴露的永遠(yuǎn)是代理對象。 - 原始對象不會被銷毀,它作為代理的目標(biāo)對象長期存在。
- 原始對象被代理對象持有(通過
- 自注入的引用關(guān)系:
- 原始對象中注入的是代理對象(通過三級緩存的工廠提前暴露)。
- 代理對象內(nèi)部持有原始對象的引用(形成“代理 → 原始對象 → 代理”的引用鏈)。
- 最終狀態(tài):
- 一級緩存中是代理對象,原始對象僅作為代理的目標(biāo)存在。
- 所有依賴
UserService的 Bean 獲得的都是代理對象。
理解誤區(qū):原始對象和代理對象之間相互引用就是循環(huán)依賴,因?yàn)橐枚际俏ㄒ坏膶ο蟆?/span>
2. 無法用三級緩存解決的場景
? 構(gòu)造器循環(huán)依賴(Constructor Circular Dependency):
@Service public class A {
private final B b;
public A(B b) {
this.b = b;
} // 構(gòu)造器依賴 B
}
@Service public class B {
private final A a;
public B(A a) {
this.a = a;
} // 構(gòu)造器依賴 A
}- 原因:構(gòu)造器注入必須在實(shí)例化時(shí)完成依賴注入,無法提前暴露半成品 Bean。
- 結(jié)果:直接拋出
BeanCurrentlyInCreationException。
? 多例(Prototype)作用域的循環(huán)依賴:
- Spring 不處理多例 Bean 的循環(huán)依賴,直接報(bào)錯(cuò)。
3.@Autowired指定構(gòu)造器注入
- 默認(rèn)行為:如果類只有一個(gè)構(gòu)造器,Spring 會自動用它注入依賴。
- 多構(gòu)造器時(shí):需要用
@Autowired明確指定哪個(gè)構(gòu)造器生效:
@Service
public class UserService {
private final OrderService orderService;
@Autowired // 顯式指定帶參構(gòu)造器
public UserService(OrderService orderService) {
this.orderService = orderService;
}
public UserService() {} // 無參構(gòu)造器(默認(rèn)不生效)
}- 優(yōu)點(diǎn):強(qiáng)制依賴、不可變(
final字段)、易于測試。
4.@Value注入配置
- 默認(rèn)行為:如果配置不存在,Spring 會拋出
IllegalArgumentException。 - 解決方案:
- 設(shè)置默認(rèn)值(推薦):
@Value("${api.key:default-value}")
private String apiKey; // 配置缺失時(shí)使用 "default-value"- 允許
null:
@Value("${api.key:#{null}}")
private String apiKey; // 配置缺失時(shí)注入 nul- 手動檢查(復(fù)雜場景):
private String apiKey;
public UserService(Environment env) {
this.apiKey = env.getProperty("api.key", "default-value");
}總結(jié)對比表
| 場景 | 能否解決循環(huán)依賴 | 推薦方案 |
|---|---|---|
| 字段/Setter 注入 | ?(三級緩存) | 可選依賴、快速開發(fā) |
| 構(gòu)造器注入 | ?(直接報(bào)錯(cuò)) | 強(qiáng)制依賴、核心業(yè)務(wù)邏輯 |
@Value 注入配置 | 不涉及循環(huán)依賴 | 加默認(rèn)值(${key:default}) |
| 多例 Bean 循環(huán)依賴 | ?(直接報(bào)錯(cuò)) | 避免多例 Bean 互相依賴 |
最佳實(shí)踐:
- 優(yōu)先用構(gòu)造器注入(強(qiáng)制依賴 + 不可變性)。
- 循環(huán)依賴改用 Setter/字段注入 或
@Lazy。 @Value務(wù)必設(shè)置默認(rèn)值,避免因配置缺失導(dǎo)致啟動失敗。
到此這篇關(guān)于Spring 依賴注入與循環(huán)依賴總結(jié)的文章就介紹到這了,更多相關(guān)Spring 依賴注入與循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java對象布局(JOL)實(shí)現(xiàn)過程解析
這篇文章主要介紹了Java對象布局(JOL)實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
SpringBoot返回統(tǒng)一的JSON標(biāo)準(zhǔn)格式實(shí)現(xiàn)步驟
這篇文章主要介紹了SpringBoot返回統(tǒng)一的JSON標(biāo)準(zhǔn)格式,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08
Spring Cloud Alibaba 本地調(diào)試介紹及方案設(shè)計(jì)
為了解決 本地調(diào)試 的問題,本文實(shí)現(xiàn)了一種簡單實(shí)用的策略,可以通過 Nacos 動態(tài)配置服務(wù)路由,還可以基于用戶,部門,組織等級別配置服務(wù)路由,實(shí)現(xiàn) 本地調(diào)試 的同時(shí),實(shí)際上也實(shí)現(xiàn) 灰度發(fā)布,感興趣的朋友跟隨小編一起看看吧2021-07-07
Java SPI 機(jī)制知識點(diǎn)總結(jié)
在本篇文章里小編給大家整理的是一篇關(guān)于Java SPI 機(jī)制知識點(diǎn)總結(jié)內(nèi)容,需要的朋友們可以參考下。2020-02-02
Spring切面優(yōu)先級與基于xml的AOP實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Spring切面的優(yōu)先級與基于xml的AOP的詳細(xì)步驟,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-11-11

