【2025最近Java面試八股】Spring中循環依賴的問題?怎么解決的?

1. 什么是循環依賴?

在Spring框架中,循環依賴是指兩個或多個bean之間相互依賴,形成了一個循環引用的情況。如果不加以處理,這種情況會導致應用程序啟動失敗。導致 Spring 容器無法完成依賴注入。
例如:

@Service
public class A {@Autowiredprivate B b;
}@Service
public class B {@Autowiredprivate A a;
}

此時,A?依賴?BB?又依賴?A,Spring 無法確定先初始化哪個 Bean。

要解決循環依賴問題的限制

Spring解決循環依賴是有一定限制的:
首先就是要求互相依賴的Bean必須要是單例的Bean;

????????為什么呢?Spring循環依賴的解決方案主要是通過對象的提前暴露來實現的。當一個對象在創建過程中需要引用到另一個正在創建的對象時,Spring會先提前暴露一個尚未完全初始化的對象實例,以解決循環依賴的問題。這個尚未完全初始化的對象實例就是半成品對象。

在 Spring 容器中,單例對象的創建和初始化只會發生一次,并且在容器啟動時就完成了。這意味著,在容器運行期間,單例對象的依賴關系不會發生變化。因此,可以通過提前暴露半成品對象的方式來解決循環依賴的問題。

相比之下,原型對象的創建和初始化可以發生多次,并且可能在容器運行期間動態地發生變化。因此,對于原型對象,提前暴露半成品對象并不能解決循環依賴的問題,因為在后續的創建過程中,可能會涉及到不同的原型對象實例,無法像單例對象那樣緩存并復用半成品對象。

另外就是依賴注入的方式不能都是構造函數注入的方式

為什么呢?Spring無法解決構造函數的循環依賴,是因為在對象實例化過程中,構造函數是最先被調用的而此時對象還未完成實例化,無法注入一個尚未完全創建的對象,因此Spring容器無法在構造函數注入中實現循環依賴的解決,像下面這樣

@Component

public class ClassA {

????????private final ClassB classB;

????????@Autowired

????????public ClassA(@Lazy ClassB classB) {

????????????????this.classB = classB;

????????}

????????// ...

}

@Component

public class ClassB {

????????private final ClassA classA;

????????@Autowired

????????public ClassB(ClassA classA) {

????????????????this.classA = classA;

?????????}

????????// ...

}

但是這樣可以通過一些方法解決的

1、重新設計,徹底消除循環依賴

循環依賴,一般都是設計不合理導致的,可以從根本上做一些重構,來徹底解決,

2、改成非構造器注入

可以改成setter注入或者字段注入。

3、使用@Lazy解決(解決構造器循環依賴的)
首先要知道 Spring利用三級緩存是無法解決構造器注入這種循環依賴的。

@Lazy 是Spring框架中的一個注解,用于延遲一個bean的初始化,直到它第一次被使用。在默認情況下,Spring容器會在啟動時創建并初始化所有的單例bean。這意味著,即使某個bean直到很晚才被使用,或者可能根本不被使用,它也會在應用啟動時被創建。@Lazy 注解就是用來改變這種行為的。

也就是說,當我們使用 @Lazy 注解時,Spring容器會在需要該bean的時候才創建它,而不是在啟動時。這意味著如果兩個bean互相依賴,可以通過延遲其中一個bean的初始化來打破依賴循環。

缺點:過度使用 @Lazy 可能會導致應用程序的行為難以預測和跟蹤,特別是在涉及多個依賴和復雜業務邏輯的情況下。

下面是一些例子

@Lazy 可以用在bean的定義上或者注入時。以下是一些使用示例:

@Component

@Lazy

public class LazyBean {

// ...

}

在這種情況下,LazyBean 只有在首次被使用時才會被創建和初始化。

@Component

public class SomeClass {

????????private final LazyBean lazyBean;

????????@Autowired

????????public SomeClass(@Lazy LazyBean lazyBean) {

????????????????this.lazyBean = lazyBean;

????????}

}

在這里,即使SomeClass在容器啟動時被創建,LazyBean也只會在SomeClass實際使用LazyBean時才被初始化。


2. Spring 如何檢測循環依賴?依賴三級緩存機制

Spring 在?創建 Bean 的流程?中會檢查循環依賴,主要通過?三級緩存(3-level cache)?機制:

  1. 一級緩存(Singleton Objects)
    存放?完全初始化好的 Bean(成品對象)。

  2. 二級緩存(Early Singleton Objects)
    存放?半成品 Bean(已實例化但未完成屬性注入)。

    而當一個對象只進行了實例化,但是還沒有進行初始化時,我們稱之為半成品對象。所以,所謂半成品對象,其實只是 bean 對象的一個空殼子,還沒有進行屬性注入和初始化。
  3. 三級緩存(Singleton Factories)
    存放?Bean 的工廠對象(用于生成代理對象,如 AOP 場景)。

如果 Spring 發現某個 Bean 正在創建中(存在于二級緩存),但又再次被依賴,則判定為循環依賴。


3. Spring 如何解決循環依賴?spring提供了一種三級緩存的機制

前面說過Spring 的三級緩存僅能解決?單例(Singleton)作用域?且?通過屬性注入(@Autowired)?的循環依賴,核心步驟如下:

首先,Spring中Bean的創建過程其實可以分成兩步,第一步叫做實例化,第二步叫做初始化

具體流程如下:

(1) 創建 Bean A 的流程
  1. 實例化 A(調用構造函數,生成原始對象)。

  2. 將 A 的工廠對象放入三級緩存(用于后續可能的 AOP 代理)。

  3. 注入 A 的依賴(發現需要 B)。

  4. 去容器中獲取 B(觸發 B 的創建)。

(2) 創建 Bean B 的流程
  1. 實例化 B(生成原始對象)。

  2. 將 B 的工廠對象放入三級緩存

  3. 注入 B 的依賴(發現需要 A)。

  4. 從三級緩存獲取 A 的工廠,生成 A 的早期引用(可能是代理對象,半成品)并放入二級緩存。

  5. B 完成屬性注入,變成一個完整 Bean,放入一級緩存。

(3) 回到 A 的創建流程
  1. 從二級緩存拿到 B 的早期引用(半成品),注入到 A。

  2. A 完成初始化,從二級緩存移除,放入一級緩存。

最終,A 和 B 都成功創建,且互相持有對方的代理或真實對象。

以下是DefaultSingletonBeanRegistry#getSingleton方法,代碼中,包括一級緩存、二級緩存、三級緩存的處理邏輯,該方法是獲取bean的單例實例對象的核心方法:

@Nullable

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

????????// 首先從一級緩存中獲取bean實例對象,如果已經存在,則直接返回

????????Object singletonObject = this.singletonObjects.get(beanName);

????????????????if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {

????????????????// 如果一級緩存中不存在bean實例對象,而且當前bean正在創建中,則從二級緩????????????????存中獲取bean實例對象

????????????????????????singletonObject = this.earlySingletonObjects.get(beanName);

????????????????????????if (singletonObject == null && allowEarlyReference) {

????????????????????????// 如果二級緩存中也不存在bean實例對象,并且允許提前引用,則需要在鎖定一級緩存之前,

????????????????????????// 先鎖定二級緩存,然后再進行一系列處理

synchronized (this.singletonObjects) {

????????????????????????// 進行一系列安全檢查后,再次從一級緩存和二級緩存中獲取bean實例對象

singletonObject = this.singletonObjects.get(beanName);

????????????????????????????????if (singletonObject == null) {

????????????????????????????????????????singletonObject = this.earlySingletonObjects.get(beanName);

????????????????????????????????????????????????if (singletonObject == null) {

????????????????????????????????????????????????????????// 如果二級緩存中也不存在bean實例對象,則從三級緩存中獲取bean的ObjectFactory,并創建bean實例對象

????????????????????????????????????????????????????????ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);

????????????????????????????????????????????????if (singletonFactory != null) {

????????????????????????????????????????????????????????singletonObject = singletonFactory.getObject();

????????????????????????????????????????????????????????// 將創建好的bean實例對象存儲到二級緩存中

????????????????????????????????????????????????????????this.earlySingletonObjects.put(beanName, singletonObject);

????????????????????????????????????????????????????????// 從三級緩存中移除bean的ObjectFactory

????????????????????????????????????????????????????????this.singletonFactories.remove(beanName);

??????????????????????????????????????????????????}

??????????????????????????????????????????}

????????????????????????????????}

????????????????????????}

????????????????}

????????}

return singletonObject;

}

Spring解決循環依賴一定需要三級緩存嗎?()

上面的流程可以看見只用到了Spring二級緩存就能解決依賴注入的問題。

其實,在大多數簡單的循環依賴場景(沒有AOP代理)中,二級緩存(Early Singleton Objects)?已經足夠解決問題。但 Spring 仍然使用?三級緩存(Singleton Factories,存放?Bean 的工廠對象(用于生成代理對象,如 AOP 場景),主要是為了處理?AOP 代理?等特殊情況,確保返回的 Bean 是經過完整代理增強的對象。AOP又是Spring中很重要的一個特性,代理不能忽略。

所以三級緩存(Singleton Factories)的核心作用是?處理 AOP 代理,確保返回的 Bean 是代理對象而非原始對象。

(1)AOP 代理的問題
如果 Bean 需要被代理(如 @Transactional、@Async),Spring 不能直接返回原始對象,而是返回代理對象。

代理對象的生成時機:需要在 Bean 初始化完成后(即 postProcessAfterInitialization 階段)。

(2)第三級緩存的作用
三級緩存存儲的是 ObjectFactory,它可以在需要時生成代理對象。

流程示例:

實例化 A(原始對象)。

將 A 的 ObjectFactory 放入三級緩存(而非原始對象)。

注入 A 的依賴時,調用 ObjectFactory.getObject():

如果 A 需要代理,則返回代理對象;

如果不需要代理,則返回原始對象。

最終放入二級緩存的是 代理對象(或原始對象),而非直接暴露原始對象。

(3)如果沒有三級緩存
如果直接將原始對象放入二級緩存,后續 AOP 代理無法替換它,導致 注入的是原始對象而非代理,可能引發問題(如事務失效)。


4. 哪些情況是三級緩存無法解決循環依賴?
場景原因
構造器注入(Constructor Injection)Spring 必須先完成構造器調用,無法提前暴露半成品 Bean。,可以使用@Lazy
原型(Prototype)作用域

Spring 不緩存原型 Bean,無法通過三級緩存機制解決。

提一點

對于原型對象,如果要解決循環依賴問題,要維護到底是哪兩個對象之間的循環依賴,解決成本變高,而且循環依賴本來就不對,所以spring不支持。

如果檢測到原型 Bean 的循環依賴,Spring 會直接報錯:

org.springframework.beans.factory.BeanCurrentlyInCreationException: 
Error creating bean with name 'A': Requested bean is currently in creation: 
Is there an unresolvable circular reference?
@Async/@Transactional 等代理類

如果循環依賴涉及 AOP 代理,可能因代理生成時機(

1:代理對象生成時機沖突,注入的可能是原始對象而非代理

2:構造器注入 + AOP 代理?? ?無法提前暴露半成品 Bean

3:原型 Bean + AOP 代理?? ?原型 Bean 無法緩存半成品

問題導致失敗(需用?@Lazy

一些極為特殊的情況。最好使用@Lazy,避免在復雜代理場景(如?@Transactional?+?@Async)中使用循環依賴


5. 如何避免或修復循環依賴?
(1) 代碼設計層面
  • 避免雙向依賴:重構代碼,使用?單向依賴?或?接口隔離

  • 提取公共邏輯:將共用邏輯抽到第三個 Bean 中。

(2) Spring 提供的解決方案
  1. 使用?@Lazy?延遲加載
    在其中一個依賴上添加?@Lazy,讓 Spring 暫時不注入真實對象,而是注入一個代理。

    @Service
    public class A {@Autowired@Lazy  // 延遲加載 Bprivate B b;
    }
  2. 改用 Setter/Field 注入
    替換構造器注入為屬性注入:

    @Service
    public class A {private B b;@Autowired  // Setter 注入public void setB(B b) { this.b = b; }
    }
  3. 使用?ApplicationContext?手動獲取 Bean
    在需要時再獲取依賴(不推薦,破壞 IoC 設計):

    @Service
    public class A {@Autowiredprivate ApplicationContext context;public void doSomething() {B b = context.getBean(B.class); // 使用時再獲取}
    }

6. 總結
要點說明
可解決的循環依賴單例 Bean + 屬性注入(@Autowired)
Spring不可自動解決的循環依賴構造器注入、原型 Bean、某些 AOP 代理場景
解決方案@Lazy、Setter 注入、代碼重構
Spring 底層機制三級緩存(Singleton Objects、Early Singleton Objects、Singleton Factories)

最佳實踐

  • 優先通過?代碼設計?避免循環依賴。

  • 必要時使用?@Lazy?或調整注入方式。

  • 避免在復雜項目中濫用循環依賴,降低維護成本。

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

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

相關文章

JimuBI 積木報表 v1.9.5發布,大屏和儀表盤,免費數據可視化

項目介紹 JimuBI (積木報表BI) 是一款免費的數據可視化產品&#xff0c;含大屏和儀表盤、門戶、移動圖表&#xff0c;像搭建積木一樣完全在線設計&#xff01; 大屏采用類word風格&#xff0c;可以隨意拖動組件&#xff0c;想怎么設計怎么設計&#xff0c;可以像百度和阿里一樣…

云原生課程-Docker

一次鏡像&#xff0c;到處運行。 1. Docker詳解&#xff1a; 1.1 Docker簡介&#xff1a; Docker是一個開源的容器化平臺&#xff0c;可以幫助開發者將應用程序和其依賴的環境打包成一個可移植的&#xff0c;可部署的容器。 docker daemon:是一個運行在宿主機&#xff08;DO…

HikariCP 6.3.0 完整配置與 Keepalive 優化指南

HikariCP 6.3.0 完整配置與 Keepalive 優化指南 HikariCP 是一個高性能、輕量級的 JDBC 連接池框架&#xff0c;廣泛應用于 Java 應用&#xff0c;尤其是 Spring Boot 項目。本文檔基于 HikariCP 6.3.0 版本&#xff0c;詳細介紹其功能、配置參數、Keepalive 機制以及優化建議…

基于springboot+vue的攝影師分享交流社區的設計與實現

開發語言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服務器&#xff1a;tomcat7數據庫&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;數據庫工具&#xff1a;Navicat11開發軟件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

ComfyUI for Windwos與 Stable Diffusion WebUI 模型共享修復

#工作記錄 雖然在安裝ComfyUI for Windwos時已經配置過extra_model_paths.yaml 文件&#xff0c;但升級ComfyUI for Windwos到最新版本后發現原先的模型配置失效了&#xff0c;排查后發現&#xff0c;原來是 extra_model_paths.yaml 文件在新版本中被移動到了C盤目錄下&#x…

【最新版】沃德代駕源碼全開源+前端uniapp

一.系統介紹 基于ThinkPHPUniapp開發的代駕軟件。系統源碼全開源&#xff0c;代駕軟件的主要功能包括預約代駕、在線搶單、一鍵定位、在線支付、車主登記和代駕司機實名登記等?。用戶可以通過小程序預約代駕服務&#xff0c;系統會估算代駕價格并推送附近代駕司機供用戶選擇&…

react的 Fiber 節點的鏈表存儲

在React Fiber架構中&#xff0c;Fiber節點的鏈表存儲是一種重要的數據結構組織方式&#xff0c;用于管理和遍歷Fiber節點。以下是關于Fiber節點鏈表存儲的詳細介紹&#xff1a; 鏈表結構 單鏈表&#xff1a;React Fiber節點通過next指針形成單鏈表結構。每個Fiber節點都有一…

Kafka + Kafka-UI

文章目錄 前言&#x1f433; 一、使用純 Kafka Kafka-UI &#xff08;無 Zookeeper&#xff09;Docker 配置&#x1f680; 啟動步驟? 服務啟動后地址&#x1f525; 注意事項&#xff08;使用 Kraft&#xff09;? NestJS Kafka 連接不變&#x1f9e0; 額外補充&#x1f4e6; …

AI聲像融合守護幼兒安全——打罵/異常聲音報警系統的智慧防護

幼兒園是孩子們快樂成長的搖籃&#xff0c;但打罵、哭鬧或尖叫等異常事件可能打破這份寧靜&#xff0c;威脅幼兒的身心安全。打罵/異常聲音報警系統&#xff0c;依托尖端的AI聲像融合技術&#xff0c;結合語音識別、情緒分析與視頻行為檢測&#xff0c;為幼兒園筑起一道智能安全…

Qt網絡數據解析方法總結

在Qt中解析網絡數據通常涉及接收原始字節流&#xff0c;并將其轉換為有意義的應用層數據。以下是詳細步驟和示例&#xff1a; 1. 網絡數據接收 使用QTcpSocket或QUdpSocket接收數據&#xff0c;通過readyRead()信號觸發讀取&#xff1a; // 創建TCP Socket并連接信號 QTcpSo…

unity編輯器的json驗證及格式化

UNITY編輯器的json格式化和驗證工具資源-CSDN文庫https://download.csdn.net/download/qq_38655924/90676188?spm1001.2014.3001.5501 反復去別的網站驗證json太麻煩了 用這個工具能方便點 # Unity JSON工具 這是一個Unity編輯器擴展&#xff0c;用于驗證、格式化和壓縮JSO…

學習筆記:Qlib 量化投資平臺框架 — FIRST STEPS

學習筆記&#xff1a;Qlib 量化投資平臺框架 — FIRST STEPS Qlib 是微軟亞洲研究院開源的一個面向人工智能的量化投資平臺&#xff0c;旨在實現人工智能技術在量化投資中的潛力&#xff0c;賦能研究&#xff0c;并創造價值&#xff0c;從探索想法到實施生產。Qlib 支持多種機器…

操作系統:計算機世界的基石與演進

一、操作系統的本質與核心功能 操作系統如同計算機系統的"總管家"&#xff0c;在硬件與應用之間架起關鍵橋梁。從不同視角觀察&#xff0c;其核心功能呈現多維價值&#xff1a; 硬件視角的雙重使命&#xff1a; 硬件管理者&#xff1a;通過內存管理、進程調度和設…

基于單片機的溫濕度采集系統(論文+源碼)

2.1系統的功能 本系統的研制主要包括以下幾項功能&#xff1a; (1)溫度檢測功能&#xff1a;對所處環境的溫度進行檢測&#xff1b; (2)濕度檢測功能&#xff1a;對所處環境的濕度進行檢測&#xff1b; (3)加熱和制冷功能&#xff1a;可以完成加熱和制冷功能。 (4)加濕和除…

webrtc使用

demo https://www.webrtc-experiment.com/ github開源demo https://github.com/muaz-khan/WebRTC-Experiment.git ws傳遞webrtc信令,本機不需要stun服務器,遠端電腦需要ice服務器建立peer連接 const WebSocket = require(ws); const express =

【數據可視化-25】時尚零售銷售數據集的機器學習可視化分析

?? 博主簡介:曾任某智慧城市類企業算法總監,目前在美國市場的物流公司從事高級算法工程師一職,深耕人工智能領域,精通python數據挖掘、可視化、機器學習等,發表過AI相關的專利并多次在AI類比賽中獲獎。CSDN人工智能領域的優質創作者,提供AI相關的技術咨詢、項目開發和個…

Python Cookbook-6.11 緩存環的實現

任務 你想定義一個固定尺寸的緩存&#xff0c;當它被填滿時&#xff0c;新加入的元素會覆蓋第一個(最老的)元素。這種數據結構在存儲日志和歷史信息時非常有用。 解決方案 當緩存填滿時&#xff0c;本節解決方案及時地修改了緩存對象&#xff0c;使其從未填滿的緩存類變成了…

OpenCv高階(九)——背景建模

目錄 一、背景建模的核心目標與核心挑戰 1. 核心目標 2. 核心挑戰 ?二、背景建模模型 1、幀差法原理 2. 概率模型&#xff08;Parametric Models&#xff09; &#xff08;1&#xff09;高斯混合模型&#xff08;Gaussian Mixture Model, GMM&#xff09; &#xff08;…

小重構,大收益!技術重構實踐:如何優雅升級老舊接口

重構格言&#xff1a;"優秀系統不是設計出來的&#xff0c;而是通過持續重構演進而來的。" —— Martin Fowler《重構&#xff1a;改善既有代碼的設計》 希望本文能為您的重構之旅提供指引&#xff0c;讓老舊系統煥發新生&#xff01; 一、背景&#xff1a;一個“穩定…

OSPF中DR/BDR的選舉

OSPF 開放式最短路徑優先協議-CSDN博客 選舉原因&#xff1a;廣播網絡中使路由信息交換更加高速有序&#xff0c;可以降低需要維護的鄰接關系數量 基本概念&#xff1a; DR (Designated Router, 指定路由器)&#xff1a;負責在廣播網絡&#xff08;以太網&#xff09;或NBMA網…