什么是 spring 的循環依賴?

什么是 spring 的循環依賴?


首先,認識一下什么是循環依賴,舉個例子:A 對象被 Spring 管理,并且引入的 B 對象,同樣的 B 對象也被 Spring 管理,并且也引入的 A 對象。這種相互被引用的情況,就是所謂的循環依賴

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

我們都知道,Spring 是將對象給管理起來,這些對象默認還都是單例的,需要的話從 Spring 中直接取出即可。

1. Spring 是如何存儲這些 Bean 呢?

Spring 通過 Map 結構將對象給緩存起來,這里的 Map 其實是分為三個:

  1. 一級緩存(singletonObjects):此緩存中的對象是已經完全創建好的,可以直接使用的 Bean;

    Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    
  2. 二級緩存(earlySingletonObjects):此緩存中存儲尚未完全初始化但已經創建了對象實例的 Bean(即提前暴露的 Bean 實例)

    Map<String, Object> earlySingletonObjects = new HashMap<>();
    
  3. 三級緩存(singletonFactories):比較特殊,存放的是 ObjectFactory,這是一個工廠,等到從緩存中取時,會執行其中的 getObject() 方法,來得到代理對象

    Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>()
    

    這樣設計是為了在有 AOP 的情況下,可以返回代理對象,而且也能滿足循環依賴。

2. 循環依賴的創建過程

  1. 當對象 a 被創建時,就會在緩存中進行查詢,先從一級緩存中進行查詢,如果沒有,接著再從二級緩存中進行查詢,如果依舊沒用,最后從第三級緩存中查詢,如果還是沒有,就接著執行后續操作;

  2. 由于緩存中沒有 a 對象,就要回到主流程,執行 a 對象的創建

    1. 利用反射創建對象
    2. 這時候就要用到三級緩存,將創建出的對象包裝成 ObjectFctory 類型,放到三級緩存中
  3. 填充 a 對象的屬性(Bean 的引入也在此執行)

    其中要引入 b 對象,同樣也要執行與 a 對象創建相同的流程:

    1. 查詢緩存

    2. b 對象的創建:利用反射創建 b 對象,并存入三級緩存中

    3. 填充 b 對象的屬性

      此時又要引入 a 對象,由于已經將 a 存儲到緩存中,因此這里要執行一些額外的操作(后面說)后,將得到的 a 對象填充到 b 對象中

    4. 初始化

    5. 緩存轉移(具體步驟后面說

  4. a 對象初始化

  5. 緩存轉移

總體流程大致如下:

3. 補充說明

3.1 在 b 對象創建中填充屬性時,從緩存中讀取 a 對象要經過什么操作?

參考源碼,讀取關鍵部分(紅框位置):


首先從三級緩存中獲取到 a 對象,由于這個緩存里存放的是 ObjectFactory 類型,并不是真正的對象,這里就要執行 getObject() 方法,從而創建出真正要使用的對象,將得到的真正的對象 a 存入二級緩存中,并將三級緩存中的 a 刪除。

3.2 緩存轉移的步驟是什么?

此時 b 已經完全創建完畢,所以要將緩存里面的對象進行轉移,參考源碼:


可以分析出具體操作為:

  1. 將 b 的完整對象放到一級緩存中
  2. 將三級緩存中的 b 移除掉
  3. 將二級緩存中的 b 移除掉(該場景下二級緩存中并沒有 b 對象)

a 對象的緩存轉移也是同理。

4. 擴展

4.1 第三級緩存的作用?

從上面的執行步驟,可以感覺到三級緩存和里面的 ObjectFactory 類型似乎有點多余,有一級、二級緩存也能搞定循環依賴,三級緩存的意義是什么?

其實主要作用就是為了 AOP。

舉例:假如對 b 對象使用了 AOP 切面功能,那么 a 對象引入的 b 對象就必須是 b 對象的代理對象,當 Spring 在沒有循環依賴的情況下,是先將普通的完整對象創建好之后,再生成對應的代理對象,然而 Spring 并沒有辦法提前知道這個對象有沒有循環依賴,也不能直接將每個對象都創建出代理對象,所以就需要吧對象包裝成 ObjectFactory 類型,提前曝光,等從三級緩存中獲取到 ObjectFactory 后,就可以通過 getObject() 方法生成代理對象。

4.2 避免循環依賴的最佳實踐

盡管 Spring 提供了循環依賴的解決方案,但在實際開發中應盡量避免循環依賴,因為它可能導致代碼耦合度過高、可維護性差等問題。以下是一些避免循環依賴的建議:

  1. 重構代碼:將公共邏輯提取到第三個類中,打破循環依賴。
  2. 使用接口或事件驅動:通過接口或事件機制解耦組件間的直接依賴。
  3. 謹慎使用構造器注入:對于可能存在循環依賴的場景,優先使用 Setter 注入

4.3 構造器注入與 Setter 注入的區別

  1. Setter 注入:支持循環依賴。因為 Spring 可以通過三級緩存機制提前暴露部分初始化的 Bean 實例
  2. 構造器注入:不支持循環依賴。原因在于構造器注入要求在創建 Bean 時必須提供所有依賴項,而循環依賴會導致死鎖(A 等待 B,B 等待 A)。

因此,如果使用構造器注入,循環依賴會導致 BeanCurrentlyInCreationException 異常。

BeanCurrentlyInCreationException 異常:表示在嘗試實例化一個 Bean 時,Spring 容器檢測到正在創建的 Bean 已經在創建過程中,導致循環依賴。這種異常通常是由于循環依賴問題引起的。

5. 總結

Spring 通過三級緩存機制解決了單例 Bean 之間的循環依賴問題,但對于原型 Bean 或構造器注入的場景,Spring 無法解決循環依賴。開發時應盡量避免循環依賴,保持代碼的清晰性和可維護性

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

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

相關文章

thrift軟件、.thrif文件和thrift協議是什么關系,有什么用

Thrift軟件、.thrift文件和Thrift協議是Apache Thrift框架的三個核心組成部分&#xff0c;它們協同實現跨語言服務的高效開發與通信。以下是三者關系及作用的詳細解析&#xff1a; 一、核心組件關系 1. Thrift軟件&#xff08;框架&#xff09; ? 定位&#xff1a;Apache Th…

STM32旋轉編碼器驅動詳解:方向判斷、卡死處理與代碼分析 | 零基礎入門STM32第四十八步

主題內容教學目的/擴展視頻旋轉編碼器電路原理&#xff0c;跳線設置&#xff0c;結構分析。驅動程序與調用。熟悉電路和驅動程序。 師從洋桃電子&#xff0c;杜洋老師 &#x1f4d1;文章目錄 一、旋轉編碼器原理與驅動結構1.1 旋轉編碼器工作原理1.2 驅動程序結構 二、方向判斷…

elementplus的cascader級聯選擇器在懶加載且多選時的一些問題分析

1. 背景 在之前做的一個項目中使用到了element的級聯選擇器&#xff0c;并且是需要懶加載、多選、父子不關聯等等&#xff0c;在選的時候當然沒問題&#xff0c;但是回顯的時候就會回顯不出來&#xff0c;相信大部分伙伴都遇到過這個問題。我在以前出過一篇文章寫過關于級聯選…

【Python運維】用Python自動化AWS資源管理:利用boto3實現高效管理S3桶和EC2實例

《Python OpenCV從菜鳥到高手》帶你進入圖像處理與計算機視覺的大門! 解鎖Python編程的無限可能:《奇妙的Python》帶你漫游代碼世界 隨著云計算的普及,AWS(Amazon Web Services)已經成為許多企業和開發者首選的云平臺。為了提高工作效率,自動化管理AWS資源成為了一個熱…

淘寶關鍵字搜索接口爬蟲測試實戰指南

在電商數據分析和市場研究中&#xff0c;通過關鍵字搜索獲取淘寶商品信息是一項重要任務。淘寶開放平臺提供了 item_search 接口&#xff0c;允許開發者通過關鍵字搜索商品&#xff0c;并獲取商品列表及相關信息。本文將詳細介紹如何設計并測試一個基于該接口的爬蟲程序&#x…

【Linux實踐系列】:用c語言實現一個shell外殼程序

&#x1f525;本文專欄&#xff1a;Linux Linux實踐項目 &#x1f338;博主主頁&#xff1a;努力努力再努力wz 那么今天我們就要進入Linux的實踐環節&#xff0c;那么我們之前學習了進程控制相關的幾個知識點&#xff0c;比如進程的終止以及進程的等待和進程的替換&#xff0c;…

?算法OJ?N-皇后問題 II【回溯剪枝】(C++實現)N-Queens II

?算法OJ?N-皇后問題【回溯剪枝】&#xff08;C實現&#xff09;N-Queens 問題描述 The n-queens puzzle is the problem of placing n n n queens on an n n n \times n nn chessboard such that no two queens attack each other. Given an integer n, return the num…

03.06 QT

一、使用QSlider設計一個進度條&#xff0c;并讓其通過線程自己動起來 程序代碼&#xff1a; <1> Widget.h: #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QThread> #include "mythread.h"QT_BEGIN_NAMESPACE namespace Ui {…

Spring WebFlux 中 WebSocket 使用 DataBuffer 的注意事項

以下是修改后的完整文檔&#xff0c;包含在多個多線程環境中使用 retain() 和 release() 方法的示例&#xff0c;且確保在 finally 塊中調用 release()&#xff1a; 在 Spring WebFlux 中&#xff0c;WebSocketMessage 主要用于表示 WebSocket 的消息載體&#xff0c;其中 getP…

【CSS】Tailwind CSS 與傳統 CSS:設計理念與使用場景對比

1. 開發方式 1.1 傳統 CSS 手寫 CSS&#xff1a;你需要手動編寫 CSS 規則&#xff0c;定義類名、ID 或元素選擇器&#xff0c;并為每個元素編寫樣式。 分離式開發&#xff1a;HTML 和 CSS 通常是分離的&#xff0c;HTML 中通過類名或 ID 引用 CSS 文件中的樣式。 示例&#…

2025華為OD機試真題E卷 - 螺旋數字矩陣【Java】

題目描述 疫情期間,小明隔離在家,百無聊賴,在紙上寫數字玩。他發明了一種寫法:給出數字個數 n (0 < n ≤ 999)和行數 m(0 < m ≤ 999),從左上角的 1 開始,按照順時針螺旋向內寫方式,依次寫出2,3,…,n,最終形成一個 m 行矩陣。小明對這個矩陣有些要求: 1、…

地下井室可燃氣體監測裝置:守護地下安全,防患于未“燃”!

在城市的地下&#xff0c;隱藏著無數的燃氣管道和井室&#xff0c;它們是城市基礎設施建設的重要部分&#xff0c;燃氣的使用&#xff0c;給大家的生活提供了極大的便利。在便利生活的背后&#xff0c;也存在潛在的城市安全隱患。 近年來&#xff0c;地下井室可燃氣體泄漏事故…

【使用hexo模板創建個人博客網站】

使用hexo模板創建個人博客網站 環境準備node安裝hexo安裝ssh配置 使用hexo命令搭建個人博客網站hexo命令 部署到github創建倉庫修改_config.yml文件 編寫博客主題擴展 環境準備 node安裝 進入node官網安裝node.js 使用node -v檢查是否安裝成功 安裝成功后應該出現如上界面 …

C# OPC DA獲取DCS數據(提前配置DCOM)

OPC DA配置操作手冊 配置完成后&#xff0c;訪問遠程ip&#xff0c;就能獲取到服務 C#使用Interop.OPCAutomation采集OPC DA數據&#xff0c;支持訂閱&#xff08;數據變化&#xff09;、單個讀取、單個寫入、斷線重連

發行思考:全球熱銷榜的頻繁變動

幾點雜感&#xff1a; 1、單機游戲銷量與在線人數的衰退是劇烈的&#xff0c;有明顯的周期性&#xff0c;而在線游戲則穩定很多。 如去年的某明星游戲&#xff0c;最高200多萬在線&#xff0c;如今在線人數是48名&#xff0c;3萬多。 而近期熱門的是MH&#xff0c;在線人數8…

Unity自定義區域UI滑動事件

自定義區域UI滑動事件 介紹制作1.創建一個Image2.創建腳本 總結 介紹 一提到滑動事件聯想到有太多的插件了比如EastTouchBundle&#xff0c;今天想單純通過UI去做一個滑動事件而不是基于Box2d或者Box去做滑動事件。 制作 1.創建一個Image 2.創建腳本 using UnityEngine; us…

taosd 寫入與查詢場景下壓縮解壓及加密解密的 CPU 占用分析

在當今大數據時代&#xff0c;時序數據庫的應用越來越廣泛&#xff0c;尤其是在物聯網、工業監控、金融分析等領域。TDengine 作為一款高性能的時序數據庫&#xff0c;憑借獨特的存儲架構和高效的壓縮算法&#xff0c;在存儲和查詢效率上表現出色。然而&#xff0c;隨著數據規模…

《UE5_C++多人TPS完整教程》學習筆記34 ——《P35 網絡角色(Network Role)》

本文為B站系列教學視頻 《UE5_C多人TPS完整教程》 —— 《P35 網絡角色&#xff08;Network Role&#xff09;》 的學習筆記&#xff0c;該系列教學視頻為計算機工程師、程序員、游戲開發者、作家&#xff08;Engineer, Programmer, Game Developer, Author&#xff09; Stephe…

K8s 1.27.1 實戰系列(七)Deployment

一、Deployment介紹 Deployment負責創建和更新應用程序的實例,使Pod擁有多副本,自愈,擴縮容等能力。創建Deployment后,Kubernetes Master 將應用程序實例調度到集群中的各個節點上。如果托管實例的節點關閉或被刪除,Deployment控制器會將該實例替換為群集中另一個節點上的…

Linux(Centos 7.6)命令詳解:vim

1.命令作用 vi/vim 是Linux 系統內置不可或缺的文本編輯命令&#xff0c;vim 是vi 的加強版本&#xff0c;兼容vi 的所有指令&#xff0c;不僅能編輯文本&#xff0c;而且還具有shell 程序編輯的功能&#xff0c;可以不同顏色的字體來辨別語法的正確性。 2.命令語法 usage: …