以下舉例皆針對(duì)單例模式討論圖解參考:https://www.processon.com/view/link/60e3b0ae0e3e74200e2478ce?1、Spring 如何創(chuàng)建 Bean?
對(duì)于單例 Bean 來(lái)說(shuō),在 Spring 容器整個(gè)生命周期內(nèi),有且只有一個(gè)對(duì)象。Spring 在創(chuàng)建 Bean 過(guò)程中,使用到了三級(jí)緩存,即 DefaultSingletonBeanRegistry.java 中定義的:
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
以 com.gyh.general 包下的 OneBean 為例,debug springboot 啟動(dòng)過(guò)程,分析 spring 是如何創(chuàng)建 bean 的。
參考圖中spring 創(chuàng)建 bean的過(guò)程。其中最關(guān)鍵的幾步有:1.getSingleton(beanName, true)依次從一二三級(jí)緩存中查找 bean 對(duì)象,如果緩存中存在對(duì)象,則直接返回 (early);2.createBeanInstance(beanName, mbd, args)選一個(gè)合適的構(gòu)造函數(shù),new 實(shí)例對(duì)象 (instance),此時(shí)的 instance 中依賴(lài)的屬性還都是 null,屬于半成品;3.singletonFactories.put(beanName, oneSingletonFactory)利用上一步的 instance,構(gòu)建一個(gè) singletonFactory,并將其放到三級(jí)緩存中;4.populateBean(beanName, mbd, instanceWrapper)填充 bean:為該 bean 定義的屬性創(chuàng)建對(duì)象或賦值;5.initializeBean("one",oneInstance, mbd)初始化 bean:對(duì) bean 進(jìn)行初始化或其他加工,如生成代理對(duì)象 (proxy);6.getSingleton(beanName, false)依次在一二級(jí)緩存中查找,檢查是否有因循環(huán)依賴(lài)導(dǎo)致提前生成的對(duì)象,有的話與初始化后的對(duì)象是否一致;2、Spring 如何解決循環(huán)依賴(lài)?
以 com.gyh.circular.threeCache 包下的 OneBean 和 TwoBean 為例 ,兩個(gè) Bean 相互依賴(lài)(即形成閉環(huán))。參考圖中spring 解決循環(huán)依賴(lài)的過(guò)程可知,spring 利用三級(jí)緩中的 objectFactory 生成并返回一個(gè) early 對(duì)象,提前暴露這個(gè) early 地址,供其他對(duì)象依賴(lài)注入使用,以此解決循環(huán)依賴(lài)問(wèn)題。3、Spring 不能解決哪些循環(huán)依賴(lài)?
3.1 循環(huán)中使用了@Async注解
3.1.1 為什么循環(huán)中使用了@Async 會(huì)報(bào)錯(cuò)?
以 com.gyh.circular.async 包下的 OneBean 和 TwoBean 為例,兩個(gè) bean 相互依賴(lài),且 oneBean 中的方法使用了@Async 注解,此時(shí)啟動(dòng) spring 失敗,報(bào)錯(cuò)信息為:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a.one': Bean with name 'a.one' has been injected into other beans [a.two] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.并通過(guò) debug 代碼,發(fā)現(xiàn)報(bào)錯(cuò)位置在 AbstractAutowireCapableBeanFactory#doCreateBean 方法內(nèi),由于 earlySingletonReference != null 且 exposedObject != bean,導(dǎo)致報(bào)錯(cuò)。
結(jié)合流程圖中spring 解決循環(huán)依賴(lài)及上述圖片中可知:1.行 1 中 bean 為 createBeanInstance 創(chuàng)建的實(shí)例 (address1)2.行 2 中 exposedObject 為 initializeBean 后生成的代理對(duì)象 (address2)3.行 3 中 earlySingletonReference 為 getEarlyBeanReference 時(shí)創(chuàng)建的對(duì)象【此處地址同 bean (address1)】深層原因?yàn)椋合惹?TwoBean 在 populateBean 時(shí)已經(jīng)依賴(lài)了地址為 address1 的 earlySingletonReference 對(duì)象,而此時(shí) OneBean 經(jīng)過(guò) initializeBean 之后,返回了地址為 address2 的新對(duì)象,導(dǎo)致 spring 不知道哪個(gè)才是最終版的 bean,所以報(bào)錯(cuò)。earlySingletonReference 是如何生成的,參考 getSingleton ("one", true) 過(guò)程。3.1.2 循環(huán)中使用了@Async 一定會(huì)報(bào)錯(cuò)嗎?
依然以 com.gyh.circular.async 包下的 OneBean 和 TwoBean 為例,兩個(gè) bean 相互依賴(lài),使 TwoBean (非 OneBean) 中的方法使用了@Async 注解,此時(shí)啟動(dòng) spring 成功,并未報(bào)錯(cuò)。debug 代碼可知:雖然 TwoBean 使用了 @Async 注解,但其 earlySingletonReference = null; 故不會(huì)引起報(bào)錯(cuò)。
深層原因?yàn)椋?/span>OneBean 先被創(chuàng)建,TwoBean 后創(chuàng)建,再整條鏈路中,并未在三級(jí)緩存中查找過(guò) TwoBean 的 objectFactory 。(OneBean 在創(chuàng)建過(guò)程中,被找過(guò)兩次,即 one-> two ->one;TwoBean 的創(chuàng)建過(guò)程中,只找過(guò)它一次,即 two ->one。)由此可得:@Async 造成循環(huán)依賴(lài)報(bào)錯(cuò)的先約條件為:1.循環(huán)依賴(lài)中的 Bean 使用了 @Async 注解2.且這個(gè) Bean,比循環(huán)內(nèi)其他 Bean 先創(chuàng)建。3.注:一個(gè) Bean 可能會(huì)同時(shí)存在于多個(gè)循環(huán)內(nèi);只要存在它是某個(gè)循環(huán)內(nèi)第一個(gè)被創(chuàng)建的 Bean,那么就會(huì)報(bào)錯(cuò)。3.1.3 為什么循環(huán)中使用了 @Transactional 不會(huì)報(bào)錯(cuò)?
已知使用了 @Transactional 注解的 Bean,Spring 也會(huì)為其生成代理對(duì)象,但為什么這種 Bean 在循環(huán)里時(shí)不會(huì)產(chǎn)生報(bào)錯(cuò)呢?以 com.gyh.circular.transactional 包下的 OneBean 和 TwoBean 為例,兩個(gè) Bean 相互依賴(lài),且 OneBean 中的方法使用了 @Transactional 注解,啟動(dòng) Spring 成功,并不會(huì)報(bào)錯(cuò)。debug 代碼可知,生成 OneBean 過(guò)程中,雖然 earlySingletonReference != null,但 initializeBean 之后的 exposedObject 和 原始實(shí)例的地址相同(即 initializeBean 步驟中,并未對(duì)實(shí)例生成代理),所以不會(huì)產(chǎn)生報(bào)錯(cuò)。
3.1.4 為什么同樣是代理會(huì)產(chǎn)生兩種不同的現(xiàn)象?同樣是生成代理對(duì)象,同樣是參與到循環(huán)依賴(lài)中,會(huì)產(chǎn)生不同現(xiàn)象的原因是:當(dāng)他們處在循環(huán)依賴(lài)中時(shí),生成代理的節(jié)點(diǎn)不同:1.@Transactional 在 getEarlyBeanReference 時(shí)生成代理,提前暴露出代理之后的地址(即最終地址);2.@Async 在 initializeBean 時(shí)生成代理,導(dǎo)致提前暴露出去的地址不是最終地址,造成報(bào)錯(cuò)。為什么 @Async 不能在 getEarlyBeanReference 時(shí)生成代理呢?對(duì)比下兩者執(zhí)行的代碼過(guò)程發(fā)現(xiàn):兩者都是在 AbstractAutoProxyCreator#getEarlyBeanReference 的方法對(duì)原始實(shí)例對(duì)象進(jìn)行包裝,如下圖
使用 @Transactional 的 Bean 在 create proxy 時(shí),獲取到一個(gè) advice ,隨即生成了代理對(duì)象 proxy.?
而使用 @Async 的 Bean 在 create proxy 時(shí),沒(méi)有獲取到 advice,不能被代理.
3.1.5 為什么 @Async 在 getEarlyBeanReference 時(shí)不能返回一個(gè) advice?
在 AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean 方法內(nèi),其主要做的事情有:1.找到當(dāng)前 spring 容器中所有的 Advisor2.返回適配當(dāng)前 bean 的所有 Advisor第一步返回的 Advisor 有 BeanFactoryCacheOperationSourceAdvisor 和 BeanFactoryTransactionAttributeSourceAdvisor,并無(wú)處理 Async 相關(guān)的 Advisor.刨根問(wèn)底,追查為什么第一步不會(huì)返回處理 Async 相關(guān)的 Advisor?已知使用 @Async @Transactional @Cacheable 需要提前進(jìn)行開(kāi)啟,即提前標(biāo)注 @EnableAsync、@EnableTransactionManagement、@EnableCaching 。以 @EnableTransactionManagement、@EnableCaching 為例,在其注解定義中,引入了 Selector 類(lèi),Selector 中又引入了 Configuration 類(lèi),在 Configuration 類(lèi)中,創(chuàng)建了對(duì)應(yīng) Advisor 并放到了 spring 容器中,所以第一步才能得到這兩個(gè) Advisor.而 @EnableAsync 的定義中引入的 Configuration 類(lèi),創(chuàng)建的是 AsyncAnnotationBeanPostProcessor 并非一個(gè) Advisor,所以第一步不會(huì)得到它,所以 @Async 的 bean 不會(huì)在這一步被代理。3.2 構(gòu)造函數(shù)引起的循環(huán)依賴(lài)
以 com.gyh.circular.constructor 包下的 OneBean 和 TwoBean 為例,兩個(gè)類(lèi)的構(gòu)造函數(shù)中各自依賴(lài)對(duì)方,啟動(dòng) spring,報(bào)錯(cuò):org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c.one': Requested bean is currently in creation: Is there an unresolvable circular reference?debug 代碼可知,兩個(gè) bean 在根據(jù)構(gòu)造函數(shù) new instance 時(shí),就已經(jīng)陷入的死循環(huán),無(wú)法提前暴露可用的地址,所以只能報(bào)錯(cuò)。4、如何解決以上循環(huán)依賴(lài)報(bào)錯(cuò)?
1.不用 @Async,將需要異步操作的方法,放到線程池中執(zhí)行。(推薦)2.提出 @Async 標(biāo)注的方法。(推薦)3.將使用 @Async 的方法提出到單獨(dú)的類(lèi)中,該類(lèi)只做異步處理,不做其他業(yè)務(wù)依賴(lài),即避免形成循環(huán)依賴(lài),從而解決報(bào)錯(cuò)問(wèn)題。參考 com.gyh.circular.async.extract 包。4.盡量不使用構(gòu)造函數(shù)依賴(lài)對(duì)象。(推薦)5.破壞循環(huán)(不推薦)即不形成閉環(huán),在開(kāi)發(fā)之前,規(guī)劃好對(duì)象依賴(lài),方法調(diào)用鏈,盡量做到不使用循環(huán)依賴(lài)。(較難,隨著迭代開(kāi)發(fā)不斷變化,很可能產(chǎn)生循環(huán))6.破壞創(chuàng)建順序(不推薦)7.由于使用 @Async 注解的所在類(lèi),比循環(huán)依賴(lài)內(nèi)其他類(lèi)先創(chuàng)建時(shí)才會(huì)報(bào)錯(cuò),那么想辦法使該類(lèi)不先于其他類(lèi)先創(chuàng)建,也可解決該問(wèn)題,如:@DependsOn、@Lazy審核編輯 :李倩
聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。
舉報(bào)投訴
-
源碼
+關(guān)注
關(guān)注
8文章
685瀏覽量
31317 -
spring
+關(guān)注
關(guān)注
0文章
341瀏覽量
15935
原文標(biāo)題:從源碼層面深度剖析 Spring 循環(huán)依賴(lài)
文章出處:【微信號(hào):OSC開(kāi)源社區(qū),微信公眾號(hào):OSC開(kāi)源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
熱點(diǎn)推薦
車(chē)規(guī)藍(lán)牙模塊技術(shù)深度剖析
在汽車(chē)電子化迅猛發(fā)展的當(dāng)下, 車(chē)規(guī)藍(lán)牙模塊 ——這一集成藍(lán)牙功能的PCBA(印刷電路板組裝件),已成為推動(dòng)汽車(chē)智能化、網(wǎng)聯(lián)化的核心力量。本文將從技術(shù)層面深入剖析車(chē)規(guī)藍(lán)牙模塊的應(yīng)用、性能指標(biāo)、安全性
CAN協(xié)議的深度剖析
單元(ECU)之間的高效通信問(wèn)題。本文將從技術(shù)原理、幀結(jié)構(gòu)、錯(cuò)誤處理機(jī)制、應(yīng)用場(chǎng)景及未來(lái)發(fā)展趨勢(shì)等方面,對(duì)CAN協(xié)議進(jìn)行深度剖析。
OPC UA協(xié)議深度剖析
。本文將從協(xié)議架構(gòu)、核心技術(shù)、應(yīng)用場(chǎng)景及未來(lái)發(fā)展趨勢(shì)等維度進(jìn)行全面剖析,幫助讀者深入理解這一工業(yè)通信領(lǐng)域的核心標(biāo)準(zhǔn)。
串口協(xié)議的深度剖析
串口通信協(xié)議作為電子設(shè)備間數(shù)據(jù)交互的基礎(chǔ)技術(shù),自20世紀(jì)60年代誕生以來(lái),始終在工業(yè)控制、嵌入式系統(tǒng)和物聯(lián)網(wǎng)等領(lǐng)域扮演著核心角色。本文將從技術(shù)原理、協(xié)議架構(gòu)、應(yīng)用場(chǎng)景及未來(lái)演進(jìn)四個(gè)維度,對(duì)串口協(xié)議展開(kāi)深度剖析。
深度剖析TL431與TL432:從特性到應(yīng)用的全方位指南
深度剖析TL431與TL432:從特性到應(yīng)用的全方位指南 在電子工程師的日常設(shè)計(jì)工作中,電壓參考器件是不可或缺的組成部分。TI的TL431和TL432作為經(jīng)典的精密可編程參考器件,憑借其出色的性能
德州儀器PCM1789-Q1音頻DAC深度剖析:從特性到應(yīng)用
德州儀器PCM1789-Q1音頻DAC深度剖析:從特性到應(yīng)用 在音頻領(lǐng)域,一款高性能的數(shù)字-to-模擬轉(zhuǎn)換器(DAC)對(duì)于音質(zhì)的提升起著至關(guān)重要的作用。今天,我們就來(lái)深入了解一下德州儀器(Texas
深度剖析LT1368運(yùn)放:特性、應(yīng)用與設(shè)計(jì)要點(diǎn)
深度剖析 LT1366/LT1367/LT1368/LT1369 系列運(yùn)放:特性、應(yīng)用與設(shè)計(jì)要點(diǎn) 引言 在電子工程師的日常設(shè)計(jì)工作中,運(yùn)算放大器是不可或缺的基本元件之一。而 Linear
可重觸發(fā)單穩(wěn)態(tài)多諧振蕩器:從原理到應(yīng)用的深度剖析
可重觸發(fā)單穩(wěn)態(tài)多諧振蕩器:從原理到應(yīng)用的深度剖析 在電子電路設(shè)計(jì)領(lǐng)域,可重觸發(fā)單穩(wěn)態(tài)多諧振蕩器是一種非常實(shí)用的電路元件,它能夠在特定的觸發(fā)條件下產(chǎn)生精確的脈沖信號(hào),廣泛應(yīng)用于脈沖整形、定時(shí)、延時(shí)等
高速利器:AD8465 LVDS 比較器深度剖析
高速利器:AD8465 LVDS 比較器深度剖析 在現(xiàn)代高速電子系統(tǒng)的設(shè)計(jì)中,比較器作為關(guān)鍵組件,其性能直接決定了系統(tǒng)的穩(wěn)定性與響應(yīng)速度。今天,我將為大家深入剖析Analog Devices
PCA9546A:低電壓4通道I2C和SMBus開(kāi)關(guān)的深度剖析
PCA9546A:低電壓4通道I2C和SMBus開(kāi)關(guān)的深度剖析 在電子設(shè)計(jì)領(lǐng)域,I2C總線的應(yīng)用極為廣泛,而PCA9546A作為一款低電壓4通道I2C和SMBus開(kāi)關(guān),以其獨(dú)特的功能和特性,為工程師
TLE989x EvalBoard with TQFP/LQFP spring socket v01_1 評(píng)估板深度解析
TLE989x EvalBoard with TQFP/LQFP spring socket v01_1 評(píng)估板深度解析 在電子設(shè)計(jì)領(lǐng)域,評(píng)估板是我們探索和驗(yàn)證新器件性能的重要工具。今天,我們就來(lái)
一款基于Java+Spring Boot+Vue的智慧隨訪管理系統(tǒng)源碼
智慧隨訪管理系統(tǒng)源碼,一款基于Java+Spring Boot+Vue的B/S架構(gòu)醫(yī)院隨訪管理系統(tǒng)源碼,采用前后端分離技術(shù)(Ant-Design+MySQL5),具有自主版權(quán)和落地案例。 隨訪管理
從設(shè)計(jì)到部署:AI即服務(wù)平臺(tái)開(kāi)發(fā)深度剖析
從頂層設(shè)計(jì)到最終部署,一個(gè)成功的AI即服務(wù)平臺(tái)開(kāi)發(fā)是一項(xiàng)復(fù)雜的系統(tǒng)工程,它融合了軟件工程、數(shù)據(jù)科學(xué)和運(yùn)維技術(shù)的精髓。
力芯微高壓LDO系列技術(shù)深度剖析:從電路架構(gòu)到場(chǎng)景適配邏輯
力芯微高壓LDO系列技術(shù)深度剖析 ,能覆蓋多場(chǎng)景需求,關(guān)鍵在于突破“寬壓適配-低功耗平衡-噪聲抑制”技術(shù)矛盾, 從電路架構(gòu)到場(chǎng)景適配邏輯 如下: 寬輸入電壓的實(shí)現(xiàn):高壓耐受性設(shè)計(jì)? ET5H7XX
從接口到架構(gòu):工控一體機(jī)定制化的深度技術(shù)剖析
在工業(yè)4.0與數(shù)字化轉(zhuǎn)型的浪潮中,工控一體機(jī)作為工業(yè)自動(dòng)化與信息化融合的核心載體,正通過(guò)深度定制化技術(shù)重構(gòu)工業(yè)控制系統(tǒng)的底層邏輯。從硬件接口的靈活配置到系統(tǒng)架構(gòu)的模塊化設(shè)計(jì),定制化已成為解決復(fù)雜
從源碼層面深度剖析Spring循環(huán)依賴(lài)
評(píng)論