Springboot怎么解決循環(huán)依賴(基于單例 Bean)
一、什么是循環(huán)依賴?
循環(huán)依賴(Circular Dependency) 指兩個(gè)或多個(gè) Bean 之間相互依賴,導(dǎo)致在創(chuàng)建 Bean 的過(guò)程中出現(xiàn)“死循環(huán)”,增加了對(duì)象依賴的混亂性,依賴關(guān)系變得錯(cuò)綜復(fù)雜。
常見三種類型的循環(huán)依賴:
類型 | 舉例 | Spring 是否能解決 |
---|---|---|
構(gòu)造器注入循環(huán)依賴 | A → B → A(構(gòu)造方法注入) | ? |
Setter / 字段注入循環(huán)依賴 | A → B → A(@Autowired) | ? |
Prototype 范圍循環(huán)依賴 | A(原型) → B(原型) → A | ? |
1. 構(gòu)造器注入循環(huán)依賴(Spring ?無(wú)法解決)
@Component public class A { private final B b; @Autowired public A(B b) { this.b = b; } } @Component public class B { private final A a; @Autowired public B(A a) { this.a = a; } }
? 結(jié)果:
報(bào)錯(cuò):BeanCurrentlyInCreationException: Requested bean is currently in creation: Is there an unresolvable circular reference?
原因:Spring 必須先構(gòu)造 A 才能注入 B,但 B 的構(gòu)造又依賴 A,導(dǎo)致死循環(huán),無(wú)法通過(guò)三級(jí)緩存提前暴露 Bean。
2. 字段(或 setter)注入循環(huán)依賴(Spring ?能自動(dòng)解決)
@Component public class A { @Autowired private B b; } @Component public class B { @Autowired private A a; }
? 結(jié)果:
Spring 能自動(dòng)解決,應(yīng)用成功啟動(dòng)。
- 原因:Spring 會(huì)先構(gòu)造出一個(gè)“空的 A 實(shí)例”,將其工廠加入三級(jí)緩存,B 注入 A 時(shí)就能拿到早期引用,從而打破循環(huán)。
- 前提:spring.main.allow-circular-references: true,必須開啟情況下才能自動(dòng)解決。然Spring 6.0 起(包括 Spring Boot 3.x)默認(rèn)為false
3. 原型作用域的循環(huán)依賴(Spring ?無(wú)法解決)
@Component @Scope("prototype") public class A { @Autowired private B b; } @Component @Scope("prototype") public class B { @Autowired private A a; }
? 結(jié)果:
報(bào)錯(cuò):BeanCurrentlyInCreationException(創(chuàng)建過(guò)程中找不到可注入的 Bean)
原因:Spring 不緩存 prototype Bean 的創(chuàng)建過(guò)程,無(wú)法通過(guò)三級(jí)緩存解決依賴鏈,原型 Bean 不參與依賴管理。
二、Spring 如何解決循環(huán)依賴(基于單例 Bean)
Spring 采用一種經(jīng)典的 三級(jí)緩存機(jī)制(3-level cache) 來(lái)解決循環(huán)依賴。這個(gè)機(jī)制存在于DefaultSingletonBeanRegistry中。
?? 前提:僅對(duì) @Scope("singleton")
且使用字段或 setter 注入有效!
1. Bean 創(chuàng)建流程概覽(以 A → B → A 為例)
? Step-by-step:
創(chuàng)建 A 實(shí)例(構(gòu)造函數(shù)執(zhí)行);
A被標(biāo)記為“正在創(chuàng)建”,并將一個(gè)工廠(ObjectFactory)放入三級(jí)緩存;
A 依賴 B → Spring 創(chuàng)建 B;
B 構(gòu)造完成,發(fā)現(xiàn)依賴 A → 嘗試獲取 A;
Spring 發(fā)現(xiàn) A 正在創(chuàng)建 → 從三級(jí)緩存拿到 ObjectFactory 生成早期 A 對(duì)象 → 放入二級(jí)緩存;
B 成功注入 A,初始化完成;
回到 A,完成初始化。
整個(gè)過(guò)程中 Spring 使用緩存提前暴露未完成的 A 實(shí)例,從而打破了循環(huán)。
2. 三級(jí)緩存詳解
緩存層級(jí) | 名稱 | 描述 | 作用 |
---|---|---|---|
一級(jí)緩存 | singletonObjects | 完全初始化完成的 Bean | 最終返回 Bean 實(shí)例 |
二級(jí)緩存 | earlySingletonObjects | 早期曝光的 Bean 實(shí)例 | 用于依賴注入 |
三級(jí)緩存 | singletonFactories | 創(chuàng)建早期 Bean 的工廠 | 延遲暴露 Bean 引用,支持代理等 |
Spring 將 Bean 提前曝光的流程:
singletonFactories -> earlySingletonObjects -> singletonObjects
3. 核心方法說(shuō)明(來(lái)自源碼)
在 Spring 源碼中,關(guān)鍵方法如下:
// DefaultSingletonBeanRegistry.java // 一級(jí)緩存 private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 二級(jí)緩存 private final Map<String, Object> earlySingletonObjects = new HashMap<>(); // 三級(jí)緩存 private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
- 在創(chuàng)建 Bean 前:Spring 把一個(gè)生成 Bean 的工廠方法放入三級(jí)緩存。
- 在注入依賴時(shí):發(fā)現(xiàn)依賴的是一個(gè)“正在創(chuàng)建”的 Bean,就會(huì)去三級(jí)緩存中拿工廠生產(chǎn)早期對(duì)象。
- 最后再完成依賴注入,放入一級(jí)緩存,清除早期引用。
三、業(yè)務(wù)開發(fā)者解決循環(huán)依賴的方法
1、使用@Lazy懶加載依賴
使用`@Lazy`注解延遲注入依賴屬性。
@Component public class A { @Autowired @Lazy private B b; }
2、將依賴的代碼移入新類,打破依賴閉環(huán)。
A → MiddleService → B
3、在方法中動(dòng)態(tài)調(diào)用spring容器的getBean方法獲取依賴,達(dá)到延遲獲取bean,避免類中直接注入循環(huán)依賴的bean
使用 ObjectFactory 或 ApplicationContext.getBean() 延遲獲取 Bean
@Component @Scope("prototype") public class A { @Autowired private ObjectFactory<B> bFactory; public void use() { B b = bFactory.getObject(); // 延遲獲取 } }
4、改為 setter 或字段注入(避免構(gòu)造器注入)
構(gòu)造器注入是“強(qiáng)依賴”,無(wú)法提前暴露:
@Component public class A { private B b; @Autowired public void setB(B b) { this.b = b; } }
5、使用@PostConstruct 或 工廠方法延遲注入
將依賴注入放到初始化之后:
@Component public class A { private final B b; public A(B b) { this.b = b; } @PostConstruct public void init() { // 在這里安全使用 b } }
6、開啟Spring Boot循環(huán)依賴(不推薦,除非必要)。
spring: main: allow-circular-references: true
到此這篇關(guān)于Springboot怎么解決循環(huán)依賴(基于單例 Bean)的文章就介紹到這了,更多相關(guān)Springboot循環(huán)依賴內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 如何解決SpringBoot2.6及之后版本取消了循環(huán)依賴的支持問(wèn)題
- SpringBoot構(gòu)造器注入循環(huán)依賴及解決方案
- SpringBoot中@Autowired注入service時(shí)出現(xiàn)循環(huán)依賴問(wèn)題的解決方法
- SpringBoot3.x循環(huán)依賴問(wèn)題解決方案
- SpringBoot啟動(dòng)報(bào)錯(cuò)屬性循環(huán)依賴報(bào)錯(cuò)問(wèn)題的解決
- SpringBoot2.6.x默認(rèn)禁用循環(huán)依賴后的問(wèn)題解決
- 基于SpringBoot構(gòu)造器注入循環(huán)依賴及解決方式
相關(guān)文章
Java利用移位運(yùn)算將int型分解成四個(gè)byte型的方法
今天小編就為大家分享一篇關(guān)于Java利用移位運(yùn)算將int型分解成四個(gè)byte型的方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12SpringBoot+slf4j線程池全鏈路調(diào)用日志跟蹤問(wèn)題及解決思路(二)
本文主要給大家介紹如何實(shí)現(xiàn)子線程中的traceId日志跟蹤,本文通過(guò)封裝Callable為例給大家介紹的非常詳細(xì),需要的朋友一起看看吧2021-05-05解決springboot啟動(dòng)報(bào)錯(cuò)bean找不到的問(wèn)題
這篇文章主要介紹了解決springboot啟動(dòng)報(bào)錯(cuò)bean找不到原因,本文給大家分享完美解決方案,通過(guò)圖文相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03Java 判斷字符串a(chǎn)和b是否互為旋轉(zhuǎn)詞
本篇文章主要介紹了判斷字符串a(chǎn)和b是否互為旋轉(zhuǎn)詞的相關(guān)知識(shí),具有很好的參考價(jià)值。下面跟著小編一起來(lái)看下吧2017-05-05深入Java7的一些新特性以及對(duì)腳本語(yǔ)言支持API的介紹
本篇文章是對(duì)Java7的一些新特性以及對(duì)腳本語(yǔ)言支持API的概述,需要的朋友參考下2013-05-05