Spring之【循環引用】

目錄

前置知識

????????SingletonBeanRegistry

????????DefaultSingletonBeanRegistry

Spring中處理循環引用的流程分析

????????定義兩個具有循環引用特點的Bean

????????執行A的實例化

????????執行A的屬性填充(執行過程中發現A依賴B,就去執行B的實例化邏輯)

????????執行B的實例化

????????執行B的屬性填充

????????執行B的初始化

????????執行A的屬性填充(此時依賴的B已經完成了實例化和初始化放到容器的單例池中,接著執行之前沒有執行完成的A的屬性填充邏輯)

????????執行A的初始化


前置知識

SingletonBeanRegistry

該接口中定義了一些和單例Bean有關的方法

package org.springframework.beans.factory.config;public interface SingletonBeanRegistry {/*** 往容器中添加單例bean對象*/void registerSingleton(String beanName, Object singletonObject);/*** 通過bean名字獲取bean對象*/Object getSingleton(String beanName);/*** 判斷容器中是否包含某bean名字的單例bean*/boolean containsSingleton(String beanName);/*** 獲取容器中所有單例bean的名稱集合*/String[] getSingletonNames();/*** 獲取容器中單例bean對象的數量*/int getSingletonCount();
}

DefaultSingletonBeanRegistry

  • 定義了三個Map集合(也就是常說的三級緩存)
  • 定義了一個Set集合,用于存儲正在創建的單例Bean的beanName
  • 對SingletonBeanRegistry接口提供方法的實現
package org.springframework.beans.factory.support;public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {// 一級緩存private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 三級緩存private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);// 二級緩存private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);// ...// 正在創建的單例Bean的beanNameprivate final Set<String> singletonsCurrentlyInCreation =Collections.newSetFromMap(new ConcurrentHashMap<>(16));/*** 對registerSingleton方法的實現*/@Overridepublic void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {// ...synchronized (this.singletonObjects) {// 通過beanName先從一級緩存中獲取Object oldObject = this.singletonObjects.get(beanName);// 一級緩存中已經有該beanName的對象,就拋異常if (oldObject != null) {throw new IllegalStateException("Could not register object [" + singletonObject +"] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");}// 一級緩存中沒有該beanName的對象,就執行添加邏輯addSingleton(beanName, singletonObject);}}/*** 往容器中添加Bean*/protected void addSingleton(String beanName, Object singletonObject) {synchronized (this.singletonObjects) {// beanName作為key,bean對象作為value,往一級緩存中添加this.singletonObjects.put(beanName, singletonObject);// 刪除三級緩存中key為beanName的節點this.singletonFactories.remove(beanName);// 刪除二級緩存中key為beanName的節點this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}// .../*** 對getSingleton方法的實現(通過beanName獲取bean對象)*/    @Override@Nullablepublic Object getSingleton(String beanName) {// 調用重載方法return getSingleton(beanName, true);}/*** getSingleton第二個參數是boolean類型的重載方法*/@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) {// 先從一級緩存中獲取bean對象Object singletonObject = this.singletonObjects.get(beanName);// 一級緩存中沒有該beanName對應的對象并且該beanName正在被創建if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {// 從二級緩存中獲取singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {// 從三級緩存中獲取ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {// 執行三級緩存中的Lambda表達式的邏輯singletonObject = singletonFactory.getObject();// 放入二級緩存this.earlySingletonObjects.put(beanName, singletonObject);// 刪除三級緩存中key為beanName的節點this.singletonFactories.remove(beanName);}}}}// 返回bean對象return singletonObject;}/*** getSingleton的重載方法* 第二個參數為Lambda表達式,為bean的創建過程*/public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {// ...synchronized (this.singletonObjects) {// 先從一級緩存中獲取bean對象Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {// bean正在被創建,拋異常if (this.singletonsCurrentlyInDestruction) {throw new BeanCreationNotAllowedException(beanName,"Singleton bean creation not allowed while singletons of this factory are in destruction " +"(Do not request a bean from a BeanFactory in a destroy method implementation!)");}// ...// 創建bean之前,做一個標記,標記該beanName的bean正在創建中beforeSingletonCreation(beanName);boolean newSingleton = false;// ...try {// 執行Lambda的邏輯,執行bean的實例化和初始化流程singletonObject = singletonFactory.getObject();newSingleton = true;}catch (IllegalStateException ex) {// ...}catch (BeanCreationException ex) {// ...}finally {// ...// 移除該beanName正在創建對象的標識afterSingletonCreation(beanName);}if (newSingleton) {// 將完成實例化和初始化的bean對象放入單例池中addSingleton(beanName, singletonObject);}}// 返回bean對象return singletonObject;}}
}

Spring中處理循環引用的流程分析

定義兩個具有循環引用特點的Bean

package spring.demo;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** A依賴B*/
@Component
public class A {@Autowiredprivate B b;
}
package spring.demo;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;/*** B依賴A*/
@Component
public class B {@Autowiredprivate A a;
}

執行A的實例化

一、DefaultListableBeanFactory#preInstantiateSingletons

遍歷beanNames,根據beanName實例化非懶加載的單例Bean

二、AbstractBeanFactory#getBean

getBean - doGetBean

三、AbstractBeanFactory#doGetBean

先嘗試從容器中獲取,有就直接返回

四、DefaultSingletonBeanRegistry#getSingleton

此時單例池singletonObjects中并沒有beanName為a的對象,且并沒有正在創建中,所以返回null

五、AbstractBeanFactory#doGetBean

調用getSingleton方法并傳入beanName+Lambda表達式

六、DefaultSingletonBeanRegistry#getSingleton

  1. 此時singletonObjects中沒有a
  2. 標記a正在創建中
  3. 執行傳入的Lambda表達式,即createBean方法

七、AbstractAutowireCapableBeanFactory#createBean

createBean - doCreateBean

八、AbstractAutowireCapableBeanFactory#doCreateBean

  1. Bean的實例化
  2. 可以看到,Bean對象已經創建(A@2126)
  3. 添加到singletonFactories中(key為beanName,value為ObjectFactory)

九、AbstractAutowireCapableBeanFactory#getEarlyBeanReference

并非將已經實例化但未初始化的Bean對象直接放入singletonFactories(三級緩存)中,考慮到代理對象,所以放入的是Lambda表達式,也就是該方法的執行邏輯

十、DefaultSingletonBeanRegistry#addSingletonFactory

放入singletonFactories(三級緩存)中,key為beanName,value為一個Lambda表達式

執行A的屬性填充(執行過程中發現A依賴B,就去執行B的實例化邏輯)

一、AbstractAutowireCapableBeanFactory#doCreateBean

填充Bean

二、AbstractAutowireCapableBeanFactory#populateBean

  1. @Autowired注解是通過AutowiredAnnotationBeanPostProcessor解析的
  2. 這里執行AutowiredAnnotationBeanPostProcessor的postProcessProperties方法

三、AutowiredAnnotationBeanPostProcessor#postProcessProperties

找到A中的b字段需要注入

四、InjectionMetadata#inject

調用AutowiredAnnotationBeanPostProcessor中的inject方法

五、AutowiredAnnotationBeanPostProcessor#inject

調用DefaultListableBeanFactory的resolveDependency方法

六、DefaultListableBeanFactory#resolveDependency

resolveDependency - doResolveDependency

七、DefaultListableBeanFactory#doResolveDependency

  1. autowiredBeanName:依賴的beanName
  2. instanceCandidate:依賴的bean類型

八、DependencyDescriptor#resolveCandidate

調用DefaultListableBeanFactory的getBean方法,此時beanName為b,也就是接下來要執行B的實例化、屬性填充、初始化邏輯

執行B的實例化

一、AbstractBeanFactory#getBean

getBean - doGetBean

二、AbstractBeanFactory#doGetBean

此時容器中沒有b對象,返回null

三、AbstractAutowireCapableBeanFactory#createBean

createBean - doCreateBean

四、AbstractAutowireCapableBeanFactory#doCreateBean

  1. Bean的實例化
  2. 實例化后的Bean對象和Bean的類型
  3. 將b和對應的Lambda表達式添加到三級緩存中
  4. 此時三級緩存中存在a,b兩個元素
  5. 填充b對象屬性

?

執行B的屬性填充

一、AbstractAutowireCapableBeanFactory#populateBean

同樣的,通過AutowiredAnnotationBeanPostProcessor的postProcessProperties方法中處理@Autowired注解

二、AutowiredAnnotationBeanPostProcessor#postProcessProperties

找到B依賴A

三、InjectionMetadata#inject

執行AutowiredAnnotationBeanPostProcessor的inject方法進行屬性注入

四、AutowiredAnnotationBeanPostProcessor#inject

調用DefaultListableBeanFactory的resolveDependency方法

五、DefaultListableBeanFactory#resolveDependency

resolveDependency - doResolveDependency

六、DefaultListableBeanFactory#doResolveDependency

獲取到注入的beanName和類型,調用DependencyDescriptor的resolveCandidate方法

七、DependencyDescriptor#resolveCandidate

從容器中獲取a對象

八、AbstractBeanFactory#getBean

getBean - doGetBean

九、DefaultSingletonBeanRegistry#getSingleton

此時發生了改變

  1. 從singletonObjects(一級緩存)中獲取a,沒有
  2. beanName為a的Bean正在創建
  3. 從earlySingletonObjects(一級緩存)中獲取a,同樣沒有
  4. 但是,從三級緩存singletonFactories中獲取a,能獲取到,此時執行三級緩存中a對應的Lambda表達式的邏輯
  5. 執行Lambda表達式后拿到a對象
  6. 將a對象(實例化了但未進行初始化)放入二級緩存中
  7. 刪除三級緩存中的a

?

十、AutowiredAnnotationBeanPostProcessor#inject

通過反射將a對象賦值給b對象的a字段

執行B的初始化

一、AbstractAutowireCapableBeanFactory#doCreateBean

調用initializeBean執行b對象后續的初始化邏輯

二、AbstractAutowireCapableBeanFactory#initializeBean

  1. Aware接口方法的回調(BeanNameAware、BeanClassLoaderAware、BeanFactoryAware)
  2. BeanPostProcessor的初始化前方法回調
  3. InitializingBean的afterPropertiesSet方法回調和自定義的初始化方法回調
  4. BeanPostProcessor的初始化后方法回調
  5. 返回完成初始化的b對象

三、DefaultSingletonBeanRegistry#getSingleton

  1. 此時已經執行完b對應的Lambda表達式的doCreateBean的邏輯,拿到的是b已經實例化并初始化好的Bean對象
  2. 刪除b正在創建的標識
  3. 將b添加到一級緩存singletonObjects單例池中,刪除三級緩存和二級緩存中的b
  4. 返回已經實例化并初始化好的b對象

執行A的屬性填充(此時依賴的B已經完成了實例化和初始化放到容器的單例池中,接著執行之前沒有執行完成的A的屬性填充邏輯)

一、AutowireAnnotationBeanPostProcessor#inject

通過反射將b對象賦值給a對象的b字段(b對象中的a字段此時的值為完成了實例化但未進行初始化的a對象,沒有進行初始化的對象并不影響別的對象去引用,后續對a對象進行初始化即可)

執行A的初始化

一、AbstractAutowireCapableBeanFactory#doCreateBean

執行a對象的初始化邏輯

二、DefaultSingletonBeanRegistry#getSingleton

執行完a對象的初始化邏輯,將a添加到一級緩存singletonObjects中,刪除三級緩存和二級緩存中的a,至此完成了相互引用的a和b對象的實例化和初始化邏輯,并將它們放入了容器的單例池中

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

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

相關文章

LRU緩存淘汰算法的詳細介紹與具體實現

LRU&#xff08;Least Recently Used&#xff0c;最近最少使用&#xff09;是一種基于時間局部性原理的緩存淘汰策略。其核心思想是&#xff1a;最近被訪問的數據在未來更可能被再次使用&#xff0c;而最久未被訪問的數據應優先被淘汰&#xff0c;從而在有限的緩存空間內保留高…

JS-第十九天-事件(一)

一、事件基礎概念1.1 事件三要素事件源&#xff1a;觸發事件的元素事件類型&#xff1a;事件的種類&#xff08;如click、mouseover等&#xff09;事件處理程序&#xff1a;響應事件的函數1.2 事件流機制事件傳播分為三個階段&#xff1a;捕獲階段&#xff1a;事件從頂層開始&a…

Matplotlib(三)- 圖表輔助元素

文章目錄一、圖表輔助元素簡介二、坐標軸的標簽、刻度范圍和刻度標簽1. 坐標軸標簽1.1 x軸標簽1.2 y軸標簽1.3 示例&#xff1a;繪制天氣氣溫折線圖2. 刻度范圍和刻度標簽2.1 刻度范圍2.1.1 x軸刻度范圍2.1.2 y軸刻度范圍2.2 刻度標簽2.2.1 x軸刻度標簽2.2.2 y軸刻度標簽2.3 示…

【Linux基礎知識系列】第七十八篇 - 初識Nmap:網絡掃描工具

在網絡管理和安全領域&#xff0c;網絡掃描是一個不可或缺的工具。它可以幫助網絡管理員了解網絡中的設備、服務以及潛在的安全漏洞。Nmap&#xff08;Network Mapper&#xff09;是一個功能強大的開源網絡掃描工具&#xff0c;它能夠快速發現網絡中的主機、端口和服務&#xf…

EasyGBS的兩種錄像回看

EasyGBS 支持兩種錄像回看&#xff0c;即“平臺端”的錄像回看和“設備端”的錄像回看。本期我們來介紹兩者的區別和使用方法。一、平臺端錄像1、什么是平臺端錄像平臺端錄像是指由 EasyGBS 平臺直接錄制并存儲。2、配置平臺端錄像進入平臺&#xff0c;依次點擊【錄像回放】→【…

大模型學習思路推薦!

為進一步貫徹落實中共中央印發《關于深化人才發展體制機制改革的意見》和國務院印發《關于“十四五”數字經濟發展規劃》等有關工作的部署要求&#xff0c;深入實施人才強國戰略和創新驅動發展戰略&#xff0c;加強全國數字化人才隊伍建設&#xff0c;持續推進人工智能從業人員…

數據庫連接池性能優化實戰

背景我們公司正在處于某個項目的維護階段&#xff0c;領導對資源告警比較重視&#xff0c;服務器資源告警的就不說了&#xff0c;運維同學每隔一小時都會檢測線上環境的應用服務信息&#xff0c;例如&#xff1a;網關日志響應時間告警/nginx日志接口響應時間告警/日志關鍵字異常…

Excel常用函數大全,非常實用

一、數學與統計函數1. SUM作用&#xff1a;求和SUM(number1, [number2], ...)SUM(A1:A10) ? 計算A1到A10單元格的總和注意&#xff1a;自動忽略文本和空單元格2. AVERAGE作用&#xff1a;計算平均值AVERAGE(number1, [number2], ...)AVERAGE(B2:B20) ? 計算B列20個數據的平均…

性能優化(一):時間分片(Time Slicing):讓你的應用在高負載下“永不卡頓”的秘密

性能優化(一)&#xff1a;時間分片&#xff08;Time Slicing&#xff09;&#xff1a;讓你的應用在高負載下“永不卡頓”的秘密 引子&#xff1a;那張讓你瀏覽器崩潰的“無限列表” 想象一個場景&#xff1a;你需要渲染一個包含一萬個項目的列表。在我們的“看不見”的應用中&a…

《C++》STL--list容器詳解

在 C 標準模板庫(STL)中&#xff0c;list 是一個非常重要的序列容器&#xff0c;它實現了雙向鏈表的數據結構。與 vector 和 deque 不同&#xff0c;list 提供了高效的插入和刪除操作&#xff0c;特別是在任意位置。本文將深入探討 list 容器的特性、使用方法以及常見操作。 文…

Day 28:類的定義和方法

DAY 28 類的定義和方法 知識點學習 1. 類的定義 在Python中&#xff0c;類是創建對象的模板。使用class關鍵字來定義一個類。類名通常采用首字母大寫的命名方式&#xff08;PascalCase&#xff09;。 # 最簡單的類定義 class MyClass:pass # 使用pass占位符類的定義就像是…

OSPF綜合實驗報告冊

一、實驗拓撲二、實驗要求1、R4為ISP&#xff0c;其上只配置IP地址&#xff1b;R4與其他所直連設備間均使用公有IP&#xff1b; 2、R3-R5、R6、R7為MGRE環境&#xff0c;R3為中心站點&#xff1b; 3、整個OSPF環境IP基于172.16.0.0/16劃分&#xff1b;除了R12有兩個環回&#x…

網絡層6——內部網關協議RIP、OSPF(重點)

目錄 一、基本概念 1、理想的路由算法應具備的特點 2、分層次的路由選擇協議 二、內部網關協議RIP 1、特點 2、路由交換信息 3、距離向量算法 4、壞消息傳送慢問題 5、RIP報文格式 三、內部網關協議OSPF 1、特點 2、其他特點 3、自治系統區域劃分 4、OSPF的5中分…

同品牌的系列廣告要如何保證宣傳的連貫性?

對于品牌的系列廣告而言&#xff0c;內容的連貫性十分重要。如果系列廣告之間缺乏內在聯系&#xff0c;不僅會削弱品牌形象的統一性&#xff0c;還可能導致用戶的認知混亂。保證宣傳內容的連貫性不是讓每則廣告完全相同&#xff0c;而是在變化中保持核心要素的一致性。我們該如…

深度學習:激活函數Activaton Function

一、為什么需要激活函數&#xff1f;神經網絡本質上是多個線性變換&#xff08;矩陣乘法&#xff09;疊加。如果沒有激活函數&#xff0c;即使疊加多層&#xff0c;整體仍等價于一個線性函數&#xff1a;這樣的網絡無法學習和擬合現實世界中復雜的非線性關系。激活函數的作用&a…

deepseek: 切分類和長函數到同名文件中

import re import sys import os import ast from tokenize import generate_tokens, COMMENT, STRING, NL, INDENT, DEDENT import iodef extract_entities(filename):"""提取類和函數到單獨文件"""with open(filename, r, encodingutf-8) as f…

新型融合肽遞送外泌體修飾可注射溫敏水凝膠用于骨再生

溫敏水凝膠因能模擬細胞外基質微環境&#xff0c;且具有原位注射性和形態適應性&#xff0c;在骨組織工程中應用廣泛。小腸黏膜下層&#xff08;SIS&#xff09;作為天然細胞外基質來源&#xff0c;富含 I 型和 III 型膠原蛋白及多種生物活性因子&#xff0c;其制備的水凝膠在組…

SPI接口的4種模式(根據時鐘極性和時鐘相位)

SPI&#xff08;Serial Peripheral Interface&#xff09; 接口根據時鐘極性&#xff08;CPOL&#xff09;和時鐘相位&#xff08;CPHA&#xff09;的不同組合&#xff0c;共有 4種工作模式。這些模式決定了數據采樣和傳輸的時序關系&#xff0c;是SPI通信中必須正確配置的關鍵…

Java:高頻面試知識分享2

HashSet 和 TreeSet 的區別&#xff1f;底層實現&#xff1a;HashSet 基于 HashMap 實現&#xff0c;使用哈希表存儲元素&#xff1b;TreeSet 基于 TreeMap&#xff0c;底層為紅黑樹。元素順序&#xff1a;HashSet 無序&#xff1b;TreeSet 會根據元素的自然順序或傳入的 Compa…

C語言習題講解-第九講- 常見錯誤分類等

C語言習題講解-第九講- 常見錯誤分類等1. C程序常見的錯誤分類不包含&#xff1a;&#xff08; &#xff09;2. 根據下面遞歸函數&#xff1a;調用函數 Fun(2) &#xff0c;返回值是多少&#xff08; &#xff09;3. 關于遞歸的描述錯誤的是&#xff1a;&#xff08; &#x…