springboot配置允許循環(huán)依賴問題
springboot配置允許循環(huán)依賴
循環(huán)依賴報(bào)錯(cuò)
當(dāng)啟動(dòng)項(xiàng)目時(shí),可能出現(xiàn)程序不能啟動(dòng)的情況,查看調(diào)試日志
會(huì)提示 :
The dependencies of some of the beans in the application context form a cycle...
原因
SpringBoot 從 2.6 開始默認(rèn)不允許出現(xiàn) Bean 循環(huán)引用。而且這個(gè)是在Bean 定義上也就是類上就不允許出現(xiàn)循環(huán)引用。
解決方式
1、允許循環(huán)引用存在
方式1.在全局配置文件設(shè)置允許循環(huán)引用存在:
spring: ? main: ? ? allow-circular-references:true? ? ?
方式2.在SpringApplicationBuilder 添加設(shè)置允許循環(huán)引用:
public static void main(String[] args) {
? new SpringApplicationBuilder(DemoApplication.class).allowCircularReferences(true).run(args);
}? ? ? ??2、消除循環(huán)依賴
Spring官方的初心是不希望開發(fā)者編寫循環(huán)引用的代碼,也就是說未來的某個(gè)版本可能強(qiáng)制不能使用循環(huán)引用,因此在項(xiàng)目中消除循環(huán)依賴是不得不面對(duì)的問題。
面試官:”Spring是如何解決的循環(huán)依賴?“
答:Spring通過三級(jí)緩存解決了循環(huán)依賴,其中一級(jí)緩存為單例池(singletonObjects),二級(jí)緩存為早期曝光對(duì)象earlySingletonObjects,三級(jí)緩存為早期曝光對(duì)象工廠(singletonFactories)。
當(dāng)A、B兩個(gè)類發(fā)生循環(huán)引用時(shí),在A完成實(shí)例化后,就使用實(shí)例化后的對(duì)象去創(chuàng)建一個(gè)對(duì)象工廠,并添加到三級(jí)緩存中,如果A被AOP代理,那么通過這個(gè)工廠獲取到的就是A代理后的對(duì)象,如果A沒有被AOP代理,那么這個(gè)工廠獲取到的就是A實(shí)例化的對(duì)象。
當(dāng)A進(jìn)行屬性注入時(shí),會(huì)去創(chuàng)建B,同時(shí)B又依賴了A,所以創(chuàng)建B的同時(shí)又會(huì)去調(diào)用getBean(a)來獲取需要的依賴,此時(shí)的getBean(a)會(huì)從緩存中獲取,第一步,先獲取到三級(jí)緩存中的工廠;第二步,調(diào)用對(duì)象工工廠的getObject方法來獲取到對(duì)應(yīng)的對(duì)象,得到這個(gè)對(duì)象后將其注入到B中。
緊接著B會(huì)走完它的生命周期流程,包括初始化、后置處理器等。當(dāng)B創(chuàng)建完后,會(huì)將B再注入到A中,此時(shí)A再完成它的整個(gè)生命周期。至此,循環(huán)依賴結(jié)束!
面試官:”為什么要使用三級(jí)緩存呢?二級(jí)緩存能解決循環(huán)依賴嗎?“
答:如果要使用二級(jí)緩存解決循環(huán)依賴,意味著所有Bean在實(shí)例化后就要完成AOP代理,這樣違背了Spring設(shè)計(jì)的原則,Spring在設(shè)計(jì)之初就是通過AnnotationAwareAspectJAutoProxyCreator這個(gè)后置處理器來在Bean生命周期的最后一步來完成AOP代理,而不是在實(shí)例化后就立馬進(jìn)行AOP代理。
SpringBoot禁用循環(huán)依賴
Spring的Bean管理,一直是整個(gè)體系中津津樂道的東西。尤其是Bean的循環(huán)依賴,更是很多面試官最喜歡考察的2B知識(shí)點(diǎn)之一。
但事實(shí)上,項(xiàng)目中存在Bean的循環(huán)依賴,是代碼質(zhì)量低下的表現(xiàn)。多數(shù)人寄希望于框架層來給擦屁股,造成了整個(gè)代碼的設(shè)計(jì)越來越糟,最后用一些奇技淫巧來填補(bǔ)犯下的錯(cuò)誤。
還好,SpringBoot終于受不了這種濫用,默認(rèn)把循環(huán)依賴給禁用了!
從2.6版本開始,如果你的項(xiàng)目里還存在循環(huán)依賴,SpringBoot將拒絕啟動(dòng)!

驗(yàn)證代碼小片段
為了驗(yàn)證這個(gè)功能,我們只需要兩段小代碼。
CircularDependencyA.java
@Component
@RequiredArgsConstructor
public class CircularDependencyA {
private final CircularDependencyB circB;
}CircularDependencyB.java
@Component
@RequiredArgsConstructor
public class CircularDependencyB {
private final CircularDependencyA circA;
}RequiredArgsConstructor注解,是lombok包里面的,用來實(shí)現(xiàn)簡單的構(gòu)造器注入。不出所料,當(dāng)我們啟動(dòng)代碼的時(shí)候,報(bào)錯(cuò)了~~
報(bào)錯(cuò)如下:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| circularDependencyA defined in file [cir/CircularDependencyA.class]
↑ ↓
| circularDependencyB defined in file [cir/CircularDependencyB.class]
└─────┘
Action:
Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.
當(dāng)然,有些鳥人已經(jīng)玩大了,循環(huán)依賴到處都是,改代碼變的越來越不現(xiàn)實(shí)。那你還可以通過在yaml里配置參數(shù)來臨時(shí)開啟循環(huán)依賴。
spring.main.allow-circular-references=true
看來SpringBoot對(duì)惡勢力的容忍能力還是不夠堅(jiān)決?。?/p>
繞過SpringBoot這個(gè)攔截的方法不止一種,比如使用@Lazy注解進(jìn)行延遲初始化。但這些都是治標(biāo)不治本,辜負(fù)了SpringBoot的一片苦心。
做對(duì)的事
其實(shí),我們一直把代碼往下找下去,會(huì)發(fā)現(xiàn)這個(gè)開關(guān),其實(shí)是Spring的功能。
AbstractAutowireCapableBeanFactory#allowCircularReferences /** Whether to automatically try to resolve circular references between beans. */ private boolean allowCircularReferences = true;
很長一段時(shí)間,SpringBoot這個(gè)值都是默認(rèn)為true的。但這種縱容造成了大批低質(zhì)量的代碼產(chǎn)生,以至于新員工一直在給老員工擦屁股。
把這個(gè)值默認(rèn)設(shè)置為false,是堅(jiān)持做對(duì)的事情。起碼,在工程師編寫出質(zhì)量不高的代碼時(shí),能夠知道他自己在做什么,而不是把隱患一步步的推遲,任代碼腐敗。
不得不為SpringBoot點(diǎn)個(gè)贊。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java讀寫鎖ReadWriteLock的創(chuàng)建使用及測試分析示例詳解
這篇文章主要為大家介紹了Java讀寫鎖ReadWriteLock的創(chuàng)建使用及測試分析示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01
Java結(jié)構(gòu)型設(shè)計(jì)模式之橋接模式詳細(xì)講解
橋接,顧名思義,就是用來連接兩個(gè)部分,使得兩個(gè)部分可以互相通訊。橋接模式將系統(tǒng)的抽象部分與實(shí)現(xiàn)部分分離解耦,使他們可以獨(dú)立的變化。本文通過示例詳細(xì)介紹了橋接模式的原理與使用,需要的可以參考一下2022-09-09
Java Socket編程心跳包創(chuàng)建實(shí)例解析
這篇文章主要介紹了Java Socket編程心跳包創(chuàng)建實(shí)例解析,具有一定借鑒價(jià)值,需要的朋友可以參考下2017-12-12
spring boot如何基于JWT實(shí)現(xiàn)單點(diǎn)登錄詳解
這篇文章主要介紹了spring boot如何基于JWT實(shí)現(xiàn)單點(diǎn)登錄詳解,用戶只需登錄一次就能夠在這兩個(gè)系統(tǒng)中進(jìn)行操作。很明顯這就是單點(diǎn)登錄(Single Sign-On)達(dá)到的效果,需要的朋友可以參考下2019-06-06
Springboot中MyBatisplus使用IPage和Page分頁的實(shí)例代碼
這篇文章主要介紹了Springboot中MyBatisplus使用IPage和Page分頁,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
Java網(wǎng)約車項(xiàng)目實(shí)戰(zhàn)之實(shí)現(xiàn)搶單功能詳解
本文詳細(xì)介紹了如何使用Java實(shí)現(xiàn)網(wǎng)約車項(xiàng)目的搶單功能,并提供了一個(gè)完整的代碼示例,以便讀者能夠直接運(yùn)行和參考,感興趣的朋友一起看看吧2024-12-12

