Spring無(wú)法解決循環(huán)依賴(lài)的五種場(chǎng)景分析
一、構(gòu)造器注入引發(fā)的循環(huán)依賴(lài)
1. 問(wèn)題復(fù)現(xiàn)
@Component public class ServiceA { private final ServiceB serviceB; @Autowired public ServiceA(ServiceB serviceB) { // 構(gòu)造器注入 this.serviceB = serviceB; } } @Component public class ServiceB { private final ServiceA serviceA; @Autowired public ServiceB(ServiceA serviceA) { // 構(gòu)造器注入 this.serviceA = serviceA; } }
報(bào)錯(cuò)信息:Requested bean is currently in creation: Is there an unresolvable circular reference?
2. 原理分析
- 三級(jí)緩存失效:構(gòu)造器注入要求在實(shí)例化階段完成依賴(lài)注入,而此時(shí) Bean 尚未放入三級(jí)緩存。
- 生命周期沖突:
3. 解決方案
- 方案 1:將其中一個(gè) Bean 改為 Setter / 字段注入
- 方案 2:使用 `@Lazy` 延遲加載
@Autowired public ServiceA(@Lazy ServiceB serviceB) { this.serviceB = serviceB; }
二、原型(Prototype)作用域的循環(huán)依賴(lài)
1. 問(wèn)題復(fù)現(xiàn)
@Scope("prototype") @Component public class PrototypeA { @Autowired private PrototypeB b; } @Scope("prototype") @Component public class PrototypeB { @Autowired private PrototypeA a; }
2. 原理分析
緩存機(jī)制不生效:原型 Bean 不會(huì)存入三級(jí)緩存,每次請(qǐng)求都創(chuàng)建新實(shí)例。
Spring 官方限制:明確說(shuō)明不處理原型 Bean 的循環(huán)依賴(lài)。
3. 解決方案
重構(gòu)設(shè)計(jì):避免原型 Bean 之間的循環(huán)依賴(lài)
改用單例:評(píng)估是否真的需要原型作用域
三、@Async 注解導(dǎo)致的代理沖突
1. 問(wèn)題復(fù)現(xiàn)
@Service public class AsyncServiceA { @Autowired private AsyncServiceB serviceB; @Async public void asyncMethod() { /* ... */ } } @Service public class AsyncServiceB { @Autowired private AsyncServiceA serviceA; }
2. 原理分析
代理時(shí)序問(wèn)題:
@Async
通過(guò)后置處理器生成代理,可能破壞三級(jí)緩存機(jī)制。典型錯(cuò)誤棧:
BeanCreationException: Error creating bean with name 'asyncServiceA': Bean with name 'asyncServiceA' has been injected into other beans [...] in their raw version as part of a circular reference.
3. 解決方案
- 方案 1:對(duì)異步方法所在類(lèi)使用接口代理
@Async public interface AsyncService { void asyncMethod(); } @Service public class AsyncServiceImpl implements AsyncService { /* ... */ }
- 方案 2:在注入點(diǎn)添加
@Lazy
@Autowired @Lazy private AsyncServiceA serviceA;
四、Configuration 類(lèi)之間的循環(huán)依賴(lài)
1. 問(wèn)題復(fù)現(xiàn)
@Configuration public class ConfigA { @Autowired private ConfigB configB; } @Configuration public class ConfigB { @Autowired private ConfigA configA; }
2. 原理分析
配置類(lèi)加載順序:配置類(lèi)需要優(yōu)先初始化,無(wú)法通過(guò)常規(guī)循環(huán)依賴(lài)解決。
Spring 限制:
@Configuration
類(lèi)被視為特殊 Bean,其代理機(jī)制與普通 Bean 不同。
3. 解決方案
重構(gòu)配置類(lèi):合并相關(guān)配置
使用
@DependsOn
:明確指定加載順序
@Configuration @DependsOn("configB") public class ConfigA { /* ... */ }
五、自定義 BeanPostProcessor 引發(fā)的沖突
1. 問(wèn)題復(fù)現(xiàn)
@Component public class CustomProcessor implements BeanPostProcessor { @Autowired private ServiceX x; // 依賴(lài)其他Bean }
2. 原理分析
處理器加載時(shí)序:
BeanPostProcessor
需要優(yōu)先初始化,此時(shí)普通 Bean 尚未創(chuàng)建。Spring 啟動(dòng)流程:
3. 解決方案
避免在 BeanPostProcessor 中注入其他 Bean
使用延遲注入
private ObjectProvider<ServiceX> xProvider; public Object postProcessBeforeInitialization(Object bean, String beanName) { ServiceX x = xProvider.getIfAvailable(); // ... }
六、終極解決方案工具箱
問(wèn)題類(lèi)型 | 應(yīng)急方案 | 根治方案 |
---|---|---|
構(gòu)造器循環(huán)依賴(lài) | @Lazy 注解 | 改為 Setter 注入 |
原型Bean循環(huán)依賴(lài) | 重構(gòu)作用域 | 引入中間類(lèi)抽象依賴(lài) |
AOP代理沖突 | 接口代理模式 | 調(diào)整切面作用順序 |
配置類(lèi)循環(huán)依賴(lài) | @DependsOn 指定順序 | 合并配置類(lèi) |
BeanPostProcessor依賴(lài) | ObjectProvider 延遲獲取 | 分離處理器與業(yè)務(wù)邏輯 |
結(jié)語(yǔ):跳出循環(huán)依賴(lài)的思維陷阱
Spring 的循環(huán)依賴(lài)處理機(jī)制體現(xiàn)了框架設(shè)計(jì)的高度智慧,但作為開(kāi)發(fā)者,最優(yōu)雅的解決方案往往不是技術(shù)手段,而是架構(gòu)設(shè)計(jì)。通過(guò)以下原則可從根本上避免循環(huán)依賴(lài):
單一職責(zé)原則:拆分臃腫的 Bean
依賴(lài)倒置原則:面向接口編程
層次化設(shè)計(jì):Controller -> Service -> Repository 的嚴(yán)格分層
以上就是Spring無(wú)法解決循環(huán)依賴(lài)的五種場(chǎng)景分析的詳細(xì)內(nèi)容,更多關(guān)于Spring無(wú)法解決循環(huán)依賴(lài)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java多線程編程之使用Synchronized關(guān)鍵字同步類(lèi)方法
JAVA中要想解決“臟數(shù)據(jù)”的問(wèn)題,最簡(jiǎn)單的方法就是使用synchronized關(guān)鍵字來(lái)使run方法同步,看下面的代碼,只要在void和public之間加上synchronized關(guān)鍵字2014-01-01java8 BigDecimal類(lèi)型的List求和方式
這篇文章主要介紹了java8 BigDecimal類(lèi)型的List求和方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-11-11詳解Java如何實(shí)現(xiàn)基于Redis的分布式鎖
在不同進(jìn)程需要互斥地訪問(wèn)共享資源時(shí),分布式鎖是一種非常有用的技術(shù)手段。這篇文章運(yùn)用圖文和實(shí)例代碼介紹了Java如何實(shí)現(xiàn)基于Redis的分布式鎖,文章介紹的很詳細(xì),對(duì)Java和Redis剛興趣的朋友們可以參考借鑒,下面來(lái)一起看看。2016-08-08Java陷阱之a(chǎn)ssert關(guān)鍵字詳解
這篇文章詳細(xì)介紹了Java陷阱之a(chǎn)ssert關(guān)鍵字,有需要的朋友可以參考一下2013-09-09Spring中Bean的加載與SpringBoot的初始化流程詳解
這篇文章主要介紹了Spring中Bean的加載與SpringBoot的初始化流程詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11springboot post接口接受json時(shí),轉(zhuǎn)換為對(duì)象時(shí),屬性都為null的解決
這篇文章主要介紹了springboot post接口接受json時(shí),轉(zhuǎn)換為對(duì)象時(shí),屬性都為null的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10Java如何基于EasyExcel實(shí)現(xiàn)導(dǎo)入數(shù)據(jù)校驗(yàn)并生成錯(cuò)誤信息Excel
這篇文章主要介紹了Java如何基于EasyExcel實(shí)現(xiàn)導(dǎo)入數(shù)據(jù)校驗(yàn)并生成錯(cuò)誤信息Excel,為了優(yōu)化項(xiàng)目中的文件導(dǎo)入功能,考慮構(gòu)建一個(gè)基于EasyExcel的通用Excel導(dǎo)入框架,主要解決導(dǎo)入數(shù)據(jù)的校驗(yàn)問(wèn)題,避免業(yè)務(wù)代碼中堆積大量校驗(yàn)邏輯,需要的朋友可以參考下2024-09-09Mybatis一對(duì)多與多對(duì)一查詢(xún)處理詳解
這篇文章主要給大家介紹了關(guān)于Mybatis一對(duì)多與多對(duì)一查詢(xún)處理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03