Spring Boot循環依賴的陷阱與解決方案:如何打破“Bean創建死循環”?

引言

在Spring Boot開發中,你是否遇到過這樣的錯誤信息?
The dependencies of some of the beans in the application context form a cycle
這表示你的應用出現了循環依賴。盡管Spring框架通過巧妙的機制解決了部分循環依賴問題,但在實際開發中(尤其是使用構造器注入時),開發者仍需警惕此類問題。本文將深入探討循環依賴的根源,分析Spring的解決策略,并提供多種實戰解決方案。


一、什么是循環依賴?

循環依賴指兩個或多個Bean相互依賴對方,形成一個閉環。例如:

  • ?Bean A? 的創建需要注入 ?Bean B?
  • ?Bean B? 的創建又需要注入 ?Bean A?

此時,Spring容器在初始化Bean時會陷入“死循環”。以下是一個典型示例:

@Service
public class ServiceA {private final ServiceB serviceB;public ServiceA(ServiceB serviceB) { // 構造器注入ServiceBthis.serviceB = serviceB;}
}@Service
public class ServiceB {private final ServiceA serviceA;public ServiceB(ServiceA serviceA) { // 構造器注入ServiceAthis.serviceA = serviceA;}
}

啟動應用時,Spring會拋出異常:
BeanCurrentlyInCreationException: Error creating bean with name 'serviceA': Requested bean is currently in creation


二、Spring如何解決循環依賴?

Spring通過三級緩存機制解決單例Bean的循環依賴問題:

  1. ?一級緩存?(singletonObjects):存放完全初始化好的Bean。
  2. ?二級緩存?(earlySingletonObjects):存放提前曝光的半成品Bean(僅實例化,未填充屬性)。
  3. ?三級緩存?(singletonFactories):存放Bean的工廠對象,用于生成半成品Bean。

?解決流程?(以A和B相互依賴為例):

  1. 創建A時,先實例化A(未填充屬性),并將A的工廠放入三級緩存。
  2. 填充A的屬性時發現需要B,開始創建B。
  3. 創建B時,實例化B后,發現需要A,此時從三級緩存中通過工廠獲取A的半成品對象。
  4. B完成初始化,放入一級緩存。
  5. A繼續填充B的實例,完成初始化,放入一級緩存。

?關鍵限制?:該機制僅支持單例Bean通過屬性注入的場景。?構造器注入會直接失敗!


三、為何構造器注入會導致循環依賴失敗?

構造器注入要求Bean在實例化階段立即獲得依賴對象,而三級緩存機制需要在屬性注入階段解決依賴。因此,當兩個Bean都使用構造器注入時,Spring無法提前曝光半成品Bean,導致循環依賴無法解決。


四、解決方案:打破循環依賴的四種方法
1. ?改用Setter/Field注入(謹慎使用)??

將構造器注入改為Setter或字段注入,允許Spring延遲注入依賴:

@Service
public class ServiceA {private ServiceB serviceB;@Autowired // Setter注入public void setServiceB(ServiceB serviceB) {this.serviceB = serviceB;}
}
  • ?優點?:快速解決問題。
  • ?缺點?:破壞了不可變性(字段非final),且可能掩蓋設計問題。
2. ?使用@Lazy延遲加載?

在依賴對象上添加@Lazy,告知Spring延遲初始化Bean:

@Service
public class ServiceA {private final ServiceB serviceB;public ServiceA(@Lazy ServiceB serviceB) {this.serviceB = serviceB; // 實際注入的是代理對象}
}
  • ?原理?:Spring生成代理對象,只有在首次調用時才會真正初始化目標Bean。
  • ?適用場景?:解決構造函數注入的循環依賴。
3. ?重新設計代碼結構?

通過分層或提取公共邏輯,消除循環依賴:

  • ?方案一?:引入中間層(如ServiceC),將A和B的共同依賴轉移到C。
  • ?方案二?:使用事件驅動(ApplicationEvent),解耦直接依賴。
// 事件驅動示例
@Service
public class ServiceA {@Autowiredprivate ApplicationEventPublisher eventPublisher;public void doSomething() {eventPublisher.publishEvent(new EventA());}
}@Service
public class ServiceB {@EventListenerpublic void handleEventA(EventA event) {// 處理事件}
}
4. ?使用ObjectProvider(推薦)??

在構造器中注入ObjectProvider,按需獲取依賴:

@Service
public class ServiceA {private final ServiceB serviceB;public ServiceA(ObjectProvider<ServiceB> serviceBProvider) {this.serviceB = serviceBProvider.getIfUnique();}
}
  • ?優點?:保持構造器注入的不可變性,顯式控制依賴獲取時機。
  • ?注意?:需確保依賴Bean存在且唯一。

五、最佳實踐與預防措施
  1. ?優先使用構造器注入?:保持Bean的不可變性和明確依賴,但需警惕循環依賴。
  2. ?定期檢測循環依賴?:
    • 使用IDE插件(如IntelliJ的Circular Dependencies分析)。
    • 通過Maven/Gradle插件(如spring-boot-dependencies-analysis)。
  3. ?代碼分層規范?:
    • 嚴格遵循分層架構(Controller → Service → Repository)。
    • 避免同一層內的Bean相互依賴。
  4. ?單元測試驗證?:編寫集成測試,驗證Bean的初始化過程。
@SpringBootTest
public class CircularDependencyTest {@Autowiredprivate ApplicationContext context;@Testvoid contextLoads() {// 若啟動無異常,則通過測試assertNotNull(context.getBean(ServiceA.class));}
}

六、總結

循環依賴是Spring開發中的常見陷阱,其本質是代碼設計問題。盡管Spring提供了部分解決方案,但重構代碼消除循環依賴才是根本之道。通過合理使用注入方式、代碼分層和工具檢測,開發者可以有效避免此類問題,構建高可維護性的應用。

?記住?:

  • 慎用@Lazy和Setter注入,它們可能掩蓋設計缺陷。
  • 構造器注入 + 合理分層 = 更健壯的系統!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/81153.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/81153.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/81153.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何閱讀、學習 Tcc (Tiny C Compiler) 源代碼?如何解析 Tcc 源代碼?

閱讀和解析 TCC&#xff08;Tiny C Compiler&#xff09; 的源代碼需要對編譯器的基本工作原理和代碼結構有一定的了解。以下是分步驟的指南&#xff0c;幫助你更高效地學習和理解 TCC 的源代碼&#xff1a; 1. 前置知識準備 C 語言基礎&#xff1a;TCC 是用 C 語言編寫的&…

Java Set系列集合詳解:HashSet、LinkedHashSet、TreeSet底層原理與使用場景

Java Set系列集合詳解&#xff1a;HashSet、LinkedHashSet、TreeSet底層原理與使用場景 一、Set系列集合概述 1. 核心特點 無序性&#xff1a;存取順序不一致&#xff08;LinkedHashSet除外&#xff09;。唯一性&#xff1a;元素不重復。無索引&#xff1a;無法通過索引直接訪…

解決 CentOS 7 鏡像源無法訪問的問題

在國內使用 CentOS 系統時&#xff0c;經常會遇到鏡像源無法訪問或者下載速度慢的問題。尤其是默認的 CentOS 鏡像源通常是國外的&#xff0c;如果你的網絡環境無法直接訪問國外服務器&#xff0c;就會出現無法下載包的情況。本文將介紹如何修改 CentOS 7 的鏡像源為國內鏡像源…

云計算與大數據進階 | 26、解鎖云架構核心:深度解析可擴展數據庫的5大策略與挑戰(上)

在云應用/服務的 5 層架構里&#xff0c;數據庫服務層穩坐第 4 把交椅&#xff0c;堪稱其中的 “硬核擔當”。它的復雜程度常常讓人望而生畏&#xff0c;不少人都將它視為整個架構中的 “終極挑戰”。 不過&#xff0c;也有人覺得可擴展存儲系統才是最難啃的 “硬骨頭”&#…

Linux——UDP/TCP協議理論

1. UDP協議 1.1 UDP協議格式 系統內的UDP協議結構體&#xff1a; 注1&#xff1a;UDP協議的報頭大小是確定的&#xff0c;為8字節 注2&#xff1a;可以通過報頭中&#xff0c;UDP長度將UDP協議的報頭和有效載荷分離&#xff0c;有效載荷將存儲到接收緩沖區中等待上層解析。 注…

考研復習全年規劃

25考研以330分成功上岸。 備考期間&#xff0c;我深知學習規劃的重要性&#xff0c;為大家精心整理了一份初試備考時間線任務規劃&#xff0c;希望能為正在備考的同學們提供參考。如果你對如何規劃學習路線仍感迷茫&#xff0c;不妨參考這份時間表&#xff0c;合理分配時間&…

PhpStudy | PhpStudy 環境配置 —— PhpStudy 目錄結構 環境變量配置 · Windows 篇

&#x1f31f;想了解這個工具的其它相關筆記&#xff1f;看看這個&#xff1a;[網安工具] 服務器環境配置工具 —— PhpStudy 使用手冊 在前面的章節中&#xff0c;筆者詳細介紹了如何在 Windows 和 Linux 系統中安裝 PhpStudy&#xff0c;但可能會有崽崽在安裝完成后發現依舊…

DDS(數據分發服務) 和 P2P(點對點網絡) 的詳細對比

1. 核心特性對比 維度 DDS P2P 實時性 微秒級延遲&#xff0c;支持硬實時&#xff08;如自動駕駛&#xff09; 毫秒至秒級&#xff0c;依賴網絡環境&#xff08;如文件傳輸&#xff09; 架構 去中心化發布/訂閱模型&#xff0c;節點自主發現 完全去中心化&#xff0c;節…

java中XML的使用

文章目錄 什么是XML特點XML作用XML的編寫語法基本語法特殊字符編寫 約束XML的書寫格式DTD文檔schema文檔屬性命名空間XML命名空間的作用 解析XML的方法??DOM解析XMLDOM介紹DOM解析包&#xff1a;org.w3c.dom常用接口DOM解析包的使用保存XML文件添加DOM節點修改/刪除DOM節點 S…

Spring Boot異步任務失效的8大原因及解決方案

Spring Boot異步任務失效的8大原因及解決方案 摘要:在使用Spring Boot的@Async實現異步任務時,你是否遇到過異步不生效的問題?本文總結了8種常見的異步失效場景,并提供對應的解決方案,幫助你徹底解決異步任務失效的難題。 一、異步失效的常見場景 1. 未啟用異步支持 ? …

QT6 源(104)篇一:閱讀與注釋QAction,其是窗體菜單欄與工具欄里的菜單項,先給出屬性測試,再給出成員函數測試,最后給出信號函數的學習于舉例測試

&#xff08;1&#xff09; &#xff08;2&#xff09; &#xff08;3&#xff09;接著給出成員函數測試 &#xff1a; &#xff08;4&#xff09; 給個信號函數的舉例 &#xff1a; &#xff08;5&#xff09; 謝謝

visual studio生成動態庫DLL

visual studio生成動態庫DLL 創建動態庫工程 注意 #include “pch.h” 要放在上面 完成后點擊生成 創建一個控制臺項目 設置項目附加目錄為剛才創建的動態庫工程Dll1&#xff1a; 配置附加庫目錄&#xff1a; 配置動態庫的導入庫&#xff08;.lib&#xff09;&#xff1a;鏈…

matlab多智能體網絡一致性研究

一個基于連續時間多智能體系統&#xff08;Multi-Agent Systems, MAS&#xff09;的一階一致性協議的MATLAB仿真代碼&#xff0c;包含網絡拓撲建模、一致性協議設計和收斂性分析。代碼支持固定拓撲和時變拓撲&#xff0c;適用于學術研究。 1. 基礎模型與代碼框架 (1) 網絡拓撲…

【omnet++】omnet++6.0.3中調用python

版本&#xff1a; omnet 6.0.3 Ubuntu 20.04.6 LTS omnet的installguide中對ubuntu版本是有要求的&#xff0c;找到對應版本下載即可 先安裝omnet再安裝anaconda omnet 6.0.3安裝 別在網上找教程了&#xff0c;官方的installguide手冊是最好的。按照手冊安裝一些依賴包后 so…

【C++】 —— 筆試刷題day_29

一、排序子序列 題目解析 一個數組的連續子序列&#xff0c;如果這個子序列是非遞增或者非遞減的&#xff1b;這個連續的子序列就是排序子序列。 現在給定一個數組&#xff0c;然后然我們判斷這個子序列可以劃分成多少個排序子序列。 例如&#xff1a;1 2 3 2 2 1 可以劃分成 …

UE RPG游戲開發練手 第二十七課 普通攻擊2

UE RPG游戲開發練手 第二十七課 普通攻擊2 1. 創建普通攻擊的蒙太奇動畫 2.打開4個蒙太奇動畫&#xff0c;修改插槽為FullBody,修改動畫速度 3.編輯動畫藍圖&#xff0c;插入FullBody插槽讓普通攻擊動畫得以播放 4. 編輯GA_LightAttack技能藍圖

MySQL——日志

undo log(回滾日志)&#xff1a;引擎層生成的日志&#xff0c;實現了事務的原子性&#xff0c;用于事務回滾和MVCC。redo log(重做日志)&#xff1a;引擎層生成的日志&#xff0c;實現了事務的持久性&#xff0c;用于非正常關閉的數據恢復。bin log(歸檔日志)&#xff1a;Serve…

QML 動畫控制、順序動畫與并行動畫

目錄 引言相關閱讀基礎屬性說明工程結構示例代碼解析示例1&#xff1a;手動控制動畫&#xff08;ControlledAnimation.qml&#xff09;示例2&#xff1a;順序動畫&#xff08;SequentialAnimationDemo.qml&#xff09;示例3&#xff1a;并行動畫&#xff08;ParallelAnimationD…

PowerShell 實現 conda 懶加載

問題 執行命令conda init powershell會在 profile.ps1中添加conda初始化的命令。 即使用戶不需要用到conda&#xff0c;也會初始化conda環境&#xff0c;拖慢PowerShell的啟動速度。 解決方案 本文展示了如何實現conda的懶加載&#xff0c;默認不加載conda環境&#xff0c;只…

R語言學習--Day03--數據清洗技巧

在一般情況下&#xff0c;我們都是在數據分析的需求前提下去選擇使用R語言。而實際上&#xff0c;數據分析里&#xff0c;百分之八十的工作&#xff0c;都是在數據清洗。并不只是我們平時會提到的異常值處理或者是整合格式&#xff0c;更多會涉及到將各種各樣的數據整合&#x…