Spring源碼分析の循環依賴

文章目錄

  • 前言
  • 一、循環依賴問題
  • 二、循環依賴的解決
  • 三、整體流程分析


前言

??常見的可能存在循環依賴的情況如下:

  1. 兩個bean中互相持有對方作為自己的屬性。
    在這里插入圖片描述在這里插入圖片描述??類似于:
    在這里插入圖片描述
  2. 兩個bean中互相持有對方作為自己的屬性,且在構造時就需要傳入:
    在這里插入圖片描述在這里插入圖片描述??類似于:
    在這里插入圖片描述
  3. 在某個bean中注入自身:
    在這里插入圖片描述
    ??其中第二種構造方法的循環依賴一般情況下是無解的,除非加上@Lazy注解。本篇重點分析第一種循環依賴Spring是如何解決的。

一、循環依賴問題

??Spring在創建一個bean時,簡單來說會經過實例化bean,屬性注入,初始化的操作。當出現第一種循環依賴時,可能會經歷以下的過程:
??AService

  1. 去單例池中找有無AService實例,此時沒有,執行doCreateBean
  2. createBeanInstance創建出AService實例。
  3. 執行AService實例的屬性填充。
  4. AService的初始化、初始化前。
  5. AService的初始化后。
  6. 放入單例池。

??其中在執行AService實例的屬性填充這一步,會根據@AutoWired的注入點,去尋找BService實例:

  1. 去單例池中找有無BService實例,此時沒有,執行doCreateBean
  2. createBeanInstance`創建出BService實例。
  3. 執行BService實例的屬性填充。
  4. BService的初始化、初始化前。
  5. BService的初始化后。
  6. 放入單例池。

??其中在執行BService實例的屬性填充這一步,會根據@AutoWired的注入點,發現需要填充AService實例,這就出現了循環依賴的問題。

二、循環依賴的解決

??那么Spring是如何解決循環依賴的?主要是通過三級緩存的機制去實現的:
在這里插入圖片描述singletonObjects :一級緩存 earlySingletonObjects 二級緩存 singletonFactories 三級緩存

??可以看到,首先在doCreateBean的方法中,bean實例化后,屬性填充之前,先有一段圖上的邏輯:

  • 判斷當前的bean是否是單例的,以及是否支持循環依賴(默認是true),以及singletonsCurrentlyInCreation集合中是否包含當前bean。
  • 如果滿足條件,就把當前的bean的名稱,和一段lambda表達式,放入singletonFactories集合中。
	protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");synchronized (this.singletonObjects) {//單例池中沒有該beanif (!this.singletonObjects.containsKey(beanName)) {//向單例工廠緩存當前bean名稱以及對應的lambda表達式this.singletonFactories.put(beanName, singletonFactory);//從早期單例對象的緩存中去除當前的beanthis.earlySingletonObjects.remove(beanName);//向已注冊的單例bean集合中添加當前bean名稱this.registeredSingletons.add(beanName);}}}

在這里插入圖片描述??這里的lambda表達式,主要是為了返回一個可以被外部提前訪問的 bean 實例。注意:lambda表達式不是在此處執行,而是先放入了earlySingletonObjects集合中!

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {//如果 Spring AOP 代理在這里介入,那么 getEarlyBeanReference() 可能會返回一個動態代理對象,而不是原始 beanexposedObject = bp.getEarlyBeanReference(exposedObject, beanName);}}return exposedObject;}

在這里插入圖片描述返回可能經過 AOP 代理的 bean

在這里插入圖片描述判斷是否需要AOP

??并且在執行doGetBean時,會首先執行getSingleton方法:
在這里插入圖片描述
??在getSingleton方法中,運用了雙檢鎖模式,避免在加鎖后其它線程已經創建并緩存了該 bean。并且上面提到的lambda表達式,會在此處真正地去執行

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {// 嘗試從單例池中獲取當前 bean 的實例,避免加鎖操作,提高性能Object singletonObject = this.singletonObjects.get(beanName);// 如果單例池(一級緩存)中沒有找到,并且當前 bean 正在創建過程中(循環依賴的處理)if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {// 嘗試從早期單例池(二級緩存)中獲取當前 bean(即尚未完全初始化的對象)singletonObject = this.earlySingletonObjects.get(beanName);// 如果在早期單例池中也沒有找到,并且允許獲取早期引用(通常是為了解決循環依賴)if (singletonObject == null && allowEarlyReference) {// 加鎖,避免并發創建同一個 bean 導致不一致的問題synchronized (this.singletonObjects) {// 再次檢查,避免在加鎖后其它線程已經創建并緩存了該 beansingletonObject = this.singletonObjects.get(beanName);// 如果單例池中依然沒有找到該 beanif (singletonObject == null) {// 嘗試從早期單例池中獲取,如果仍然沒有找到singletonObject = this.earlySingletonObjects.get(beanName);// 如果早期單例池中也沒有找到,嘗試從工廠中獲取 bean(即懶加載)if (singletonObject == null) {// 獲取 bean 的工廠方法(三級緩存)ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);// 如果工廠方法存在,說明該 bean 尚未初始化,且支持懶加載if (singletonFactory != null) {// 使用工廠創建 bean 并緩存到早期單例池中,防止循環依賴singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject); // 緩存早期對象this.singletonFactories.remove(beanName); // 移除工廠方法,因為 bean 已經創建}}}}}}// 返回獲取到的 singletonObject,如果沒有找到,則返回 nullreturn singletonObject;
}

??這里的isSingletonCurrentlyInCreation方法,是判斷當前的bean名稱是否在singletonsCurrentlyInCreation集合中,那么bean是在什么時候存入該集合的呢?答案是在下方重載的getSingleton方法中:
在這里插入圖片描述在這里插入圖片描述在這里插入圖片描述無論if條件是否成立,都會把當前的bean名稱放入singletonsCurrentlyInCreation集合中

??而在執行完createBean(包括實例化,依賴注入,初始化)之后,會執行addSingleton方法:
在這里插入圖片描述
??會將當前bean從二級、三級緩存中移除,并放入單例池中,表示該bean已經完全創建完成。

protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {//將當前初始化完成的bean存入單例池(一級緩存)this.singletonObjects.put(beanName, singletonObject);//從三級緩存中刪除當前beanthis.singletonFactories.remove(beanName);//從二級緩存中刪除當前beanthis.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}
}

三、整體流程分析

在這里插入圖片描述A和B的創建流程 藍色代表A 綠色代表B

??A嘗試從單例池中獲取
在這里插入圖片描述??條件不滿足,返回null
在這里插入圖片描述??進入getSingleton
在這里插入圖片描述
??進入getSingleton,執行beforeSingletonCreation,將A放入singletonsCurrentlyInCreation中,代表A正在被創建。
在這里插入圖片描述??進入doCreateBeanaddSingletonFactory方法:
在這里插入圖片描述??進入doCreateBeanaddSingletonFactory方法,將A放入三級緩存中
在這里插入圖片描述??執行A的屬性填充(A有一個屬性為B):
在這里插入圖片描述??嘗試從容器中獲取B
在這里插入圖片描述在這里插入圖片描述??這里的條件依舊不滿足,返回null:
在這里插入圖片描述??進入getSingleton,執行beforeSingletonCreation,同樣將B放入singletonsCurrentlyInCreation中,代表B正在被創建。
在這里插入圖片描述??進入doCreateBeanaddSingletonFactory

在這里插入圖片描述??同樣將B放入三級緩存
在這里插入圖片描述


??此時的緩存情況:A和B只在二級緩存中
在這里插入圖片描述


??進行B的屬性填充**(B中有一個A屬性)**
在這里插入圖片描述??再次嘗試從容器中獲取A
在這里插入圖片描述在這里插入圖片描述??此時的條件滿足:
在這里插入圖片描述??從三級緩存中取出A的lambda表達式執行:
在這里插入圖片描述在這里插入圖片描述??此時的AB屬性是沒有值的
在這里插入圖片描述??放入二級緩存,并從三級緩存中刪除:
在這里插入圖片描述


??此時的緩存情況:
在這里插入圖片描述


??直接返回,不走createBean的邏輯了。
在這里插入圖片描述
??給BA屬性賦值,完成屬性填充:
在這里插入圖片描述??B繼續執行初始化

在這里插入圖片描述??執行BgetSingletonaddSingleton方法,將B放入單例池,并且清除二三級緩存:
在這里插入圖片描述??回到A的屬性填充,填充了B屬性
在這里插入圖片描述??填充完成后,A的B屬性有值了,B的A屬性也有值了,繼續執行A的初始化,完成后將A放入單例池,并且清除二三級緩存:在這里插入圖片描述


??此時的二三級緩存全部清空:
在這里插入圖片描述


??至此整個流程全部結束。
??為什么要加入三級緩存?因為上面的過程只是普通情況,還需要考慮到AOP的情況。如果開啟了AOP,那么會在初始化后,**基于切面生成一個代理對象。**而循環依賴的觸發時機是在屬性注入時,如果只使用普通的緩存,會導致解決循環依賴時注入的對象是普通對象,而最終的對象是代理對象,產生不一致的情況。
??applyBeanPostProcessorsAfterInitialization是初始化后執行的方法,也是在AOP的場景下生成代理對象的方法:
在這里插入圖片描述??如果開啟了AOP,那么在循環中會執行AbstractAutoProxyCreatorpostProcessAfterInitialization方法生成代理,在這一步中會判斷當前Bean是否已經在解決了循環依賴的過程中進行了AOP,如果已經進行過了,就不會再次生成代理,保證代理對象的唯一性。
在這里插入圖片描述

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

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

相關文章

Docker 部署 Jenkins持續集成(CI)工具

[TOC](Docker 部署 Jenkins持續集成(CI)工具) 前言 Jenkins 是一個流行的開源自動化工具&#xff0c;廣泛應用于持續集成&#xff08;CI&#xff09;和持續交付&#xff08;CD&#xff09;的環境中。通過 Docker 部署 Jenkins&#xff0c;可以簡化安裝和配置過程&#xff0c;并…

《Effective Objective-C》閱讀筆記(中)

目錄 接口與API設計 用前綴避免命名空間沖突 提供“全能初始化方法” 實現description方法 盡量使用不可變對象 使用清晰而協調的命名方式 方法命名 ?編輯類與協議命名 為私有方法名加前綴 理解OC錯誤模型 理解NSCopying協議 協議與分類 通過委托與數據源協議進行…

C++程序員內功修煉——Linux C/C++編程技術匯總

在軟件開發的宏大版圖中&#xff0c;C 語言宛如一座巍峨的高山&#xff0c;吸引著無數開發者攀登探索。而 Linux 操作系統&#xff0c;以其開源、穩定、高效的特性&#xff0c;成為了眾多開發者鐘愛的開發平臺。將 C 與 Linux 相結合&#xff0c;就如同為開發者配備了一把無堅不…

數據庫索引:缺點與類型全解析

在數據庫的世界里&#xff0c;索引就像是一本書的目錄&#xff0c;它能幫助我們快速定位到所需的數據&#xff0c;極大地提升查詢效率。然而&#xff0c;就如同任何事物都有兩面性一樣&#xff0c;索引也并非完美無缺。今天&#xff0c;我們就來深入探討一下索引的缺點以及常見…

【python】提取word\pdf格式內容到txt文件

一、使用pdfminer提取 import os import re from pdfminer.high_level import extract_text import docx2txt import jiebadef read_pdf(file_path):"""讀取 PDF 文件內容:param file_path: PDF 文件路徑:return: 文件內容文本"""try:text ext…

嵌入式八股文(五)硬件電路篇

一、名詞概念 1. 整流和逆變 &#xff08;1&#xff09;整流&#xff1a;整流是將交流電&#xff08;AC&#xff09;轉變為直流電&#xff08;DC&#xff09;。常見的整流電路包括單向整流&#xff08;二極管&#xff09;、橋式整流等。 半波整流&#xff1a;只使用交流電的正…

精選案例展 | 智己汽車—全棧可觀測驅動智能化運營與成本優化

本案例為“觀測先鋒 2024 可觀測平臺創新應用案例大賽”精選案例&#xff0c;同時榮獲IT168“2024技術卓越獎評選-年度創新解決方案”獎。 項目背景 近年來&#xff0c;中國汽車行業進入轉型升級階段&#xff0c;智能網聯技術成為行業發展的核心。車聯網、自動駕駛等技術的加速…

速通HTML

目錄 HTML基礎 1.快捷鍵 2.標簽 HTML進階 1.列表 a.無序列表 b.有序列表 c.定義列表 2.表格 a.內容 b.合并單元格 3.表單 a.input標簽 b.單選框 c.上傳文件 4.下拉菜單 5.文本域標簽 6.label標簽 7.按鈕標簽 8.無語義的布局標簽div與span 9.字符實體 HTML…

【Python模塊】——pymysql

pymysql是python操作mysql的標準庫&#xff0c;可以通過pip install快速導入pymysql包操作數據庫 使用pymysql操作mysql 簡單demo import pymysql connect pymysql.connect(host"localhost",port3306,user"root",password"root",database&quo…

IP離線庫助力破解網絡反詐難題

毫秒級響應識別異常訪問 IP離線庫集成全球全量IP地址的詳細信息&#xff0c;包括地理地址查詢、運營商、經緯度、代理識別等多種維度數據。例如&#xff1a; 當用戶賬號頻繁從北京、越南等多地IP登錄時&#xff0c;系統將自動觸發風險預警&#xff1b; 檢測到訪問IP為已知機…

lattice hdl實現spi接口

在lattice工具鏈中實現SPI接口通常涉及以下步驟: 定義硬件SPI接口的管腳。配置SPI時鐘和模式。編寫SPI主機或從機的控制邏輯。 展示了如何在Lattice工具鏈中使用HDL語言(例如Verilog)來配置SPI接口: lattice工程 頂層:spi_slave_top.v `timescale 1ns/ 1ps module spi_…

Spring 循環依賴解析與解決方案

文章目錄 1. 什么是循環依賴&#xff1f;1.1 概念解析1.2 示例代碼 2. 循環依賴的類型2.1 構造器循環依賴&#xff08;不可解決 ?&#xff09;2.2 Setter 方式或 Autowired 方式的循環依賴&#xff08;可解決 ?&#xff09; 3. 解決循環依賴的方式3.1 方式一&#xff1a;使用…

Cesium@1.126.0,創建3D瓦片,修改樣式

第一步&#xff1a;添加3D建筑 Cesium.createOsmBuildingsAsync()這是一個異步方法&#xff0c;所以要寫在一個異步函數里 創建一個函數 const create3DBuilding async (viewer) > {try {// 添加3D建筑const tileset await Cesium.createOsmBuildingsAsync();viewer.scen…

力扣-貪心-1005 k次取反后最大化的數組和

思路 找到絕對值最大的&#xff0c;然后如果是負數就變成正的&#xff0c;所有數遍歷完之后&#xff0c;有兩種情況&#xff0c;一種是k已經為0了&#xff0c;不需要再取反了&#xff0c;一種是所有數都為正數&#xff0c;k不為0&#xff0c;此時對絕對值最小的數操作即可 代…

vue2項目打包后js文件過大, 首次加載緩慢

vue2項目打包后js文件過大, 首次加載緩慢 安裝插件 npm i compression-webpack-plugin6.1.1 -D配置vue.config.js const CompressionWebpackPlugin require(compression-webpack-plugin)module.exports {configureWebpack: {plugins:[new CompressionWebpackPlugin({filen…

高級SQL技術在Python項目中的應用:ORM與深度性能優化

引言 在現代Python項目開發中,數據庫交互遠不止是數據的簡單存取,它已成為構建高性能、可維護應用的核心瓶頸和關鍵能力所在。 僅僅依賴基礎SQL查詢,雖然入門簡單,卻難以應對日益增長的應用挑戰。這些挑戰主要體現在以下幾個方面: 性能瓶頸: 數據量劇增: 從百萬到數十億乃…

基于 C++ Qt 的 Fluent Design 組件庫 QFluentWidgets

簡介 QFluentWidgets 是一個基于 Qt 的 Fluent Designer 組件庫&#xff0c;內置超過 150 個開箱即用的 Fluent Designer 組件&#xff0c;支持亮暗主題無縫切換和自定義主題色。 編譯示例 以 Qt5 為例&#xff08;Qt6 也支持&#xff09;&#xff0c;將 libQFluentWidgets.d…

抖音視頻如何下載保存去水印

隨著短視頻平臺的興起&#xff0c;抖音作為國內最受歡迎的短視頻平臺之一&#xff0c;吸引了大量用戶上傳和觀看各種創意視頻。許多用戶在瀏覽抖音視頻時&#xff0c;往往會想要保存一些有趣或精彩的視頻片段&#xff0c;但抖音視頻通常會有水印&#xff0c;影響觀看體驗。為了…

React 源碼揭秘 | 更新隊列

前面幾篇遇到updateQueue的時候&#xff0c;我們把它先簡單的當成了一個隊列處理&#xff0c;這篇我們來詳細討論一下這個更新隊列。 有關updateQueue中的部分&#xff0c;可以見源碼 UpdateQueue實現 Update對象 我們先來看一下UpdateQueue中的內容&#xff0c;Update對象&…

[SQL] 事務的四大特性(ACID)

&#x1f384;事務的四大特性 以下就是事務的四大特性&#xff0c;簡稱ACID。 原子性&#x1f4e2;事務時不可分割的最小操作單元&#xff0c;要么全部成功&#xff0c;要么全部失敗。一致性&#x1f4e2;事務完成后&#xff0c;必須使所有的數據都保持一致隔離性&#x1f4e2…