欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring詳細(xì)講解循環(huán)依賴是什么

 更新時(shí)間:2022年08月15日 15:12:48   作者:l昨日青天  
這篇文章主要介紹了Java中的Spring循環(huán)依賴詳情,文章基于Java的相關(guān)資料展開詳細(xì)介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下

前言

Spring在我們實(shí)際開發(fā)過程中真的太重要了,當(dāng)你在公司做架構(gòu)升級、沉淀工具等都會多多少少用到Spring,本人也一樣,在研習(xí)了好幾遍Spring源碼之后,產(chǎn)生一系列的問題, 也從網(wǎng)上翻閱了各種資料,最近說服了自己,覺得還是得整理一下,有興趣的朋友可以一起討論溝通一波。回到標(biāo)題,我們要知道以下幾點(diǎn):

(1)什么是循環(huán)依賴?

(2)Spring如何解決循環(huán)依賴

(3)只用一級緩存會存在什么問題

(4)只用二級緩存會存在什么問題

(5)Spring 為什么不用二級緩存來解決循環(huán)依賴問題

什么是循環(huán)依賴

很直接的一張圖:

循環(huán)依賴分為三種:構(gòu)造器注入方式的循環(huán)依賴、setter注入方式的循環(huán)、屬性注入方式的循環(huán)依賴;

其中構(gòu)造器注入方式造成的循環(huán)依賴Spring無法解決,這一點(diǎn)可以通過調(diào)試Spring源碼得到結(jié)論。

(ps:X和Y都是構(gòu)造器注入彼此,嚴(yán)謹(jǐn)一點(diǎn))

Spring如何處理的循環(huán)依賴

Spring通過三級緩存解決了循環(huán)依賴,其中一級緩存為單例池(singletonObjects),二級緩存為早期曝光對象earlySingletonObjects,三級緩存為早期曝光對象工廠(singletonFactories)。

當(dāng)A、B兩個(gè)類發(fā)生循環(huán)引用時(shí),在A完成實(shí)例化后,就使用實(shí)例化后的對象去創(chuàng)建一個(gè)對象工廠,并添加到三級緩存中,如果A被AOP代理,那么通過這個(gè)工廠獲取到的就是A代理后的對象,如果A沒有被AOP代理,那么這個(gè)工廠獲取到的就是A實(shí)例化的對象。當(dāng)A進(jìn)行屬性注入時(shí),會去創(chuàng)建B,同時(shí)B又依賴了A,所以創(chuàng)建B的同時(shí)又會去調(diào)用getBean(a)來獲取需要的依賴,此時(shí)的getBean(a)會從緩存中獲取,第一步,先獲取到三級緩存中的工廠;第二步,調(diào)用對象工工廠的getObject方法來獲取到對應(yīng)的對象,得到這個(gè)對象后將其注入到B中。緊接著B會走完它的生命周期流程,包括初始化、后置處理器等。當(dāng)B創(chuàng)建完后,會將B再注入到A中,此時(shí)A再完成它的整個(gè)生命周期。至此,循環(huán)依賴結(jié)束,上源碼:

第一次創(chuàng)建A時(shí)

截圖處代碼不成立,直接返回,進(jìn)行A的創(chuàng)建過程

真正觸發(fā)去創(chuàng)建A的地方

這里將A加入正在創(chuàng)建的一個(gè)集合,代表A正在創(chuàng)建當(dāng)中

暴露早期A的半成品對象,如果A被AOP代理,那就是暴露A的代理對象,將改對象的創(chuàng)建工廠添加至三級緩存中,那么什么會去用呢?

進(jìn)行屬性注入,在這里發(fā)現(xiàn)需要注入B,然后進(jìn)行B的創(chuàng)建

B的創(chuàng)建同A一樣,此時(shí)就不截圖了。

然后在進(jìn)行B的屬性注入時(shí),發(fā)現(xiàn)需要注入A,再次回到A的創(chuàng)建,回到第一次創(chuàng)建的那個(gè)地方:

從三級緩存中獲取A的半成品對象,添加至二級緩存中。

此時(shí)將這個(gè)半成品對象給到B,讓B完成初始化,然后再次回到A的創(chuàng)建過程,上圖中觸發(fā)createBean的那個(gè)地方:

生成最終的A對象,放入一級緩存中。

只用一級緩存會存在什么問題

只使用一級緩存,也就是將所有的 bean 的實(shí)例都放在同一個(gè) Map 容器中。其中就包括已經(jīng)初始化好的 bean 和未初始化好的 bean。

已經(jīng)初始化好的 bean: 指經(jīng)過了 bean 創(chuàng)建的三個(gè)階段之后的 bean 對象

未初始化好的 bean : 指經(jīng)過了 bean 創(chuàng)建的第一個(gè)階段,只將 bean 實(shí)例創(chuàng)建出來了

bean 的創(chuàng)建過程分三個(gè)階段:

1、創(chuàng)建實(shí)例 createBeanInstance

2、填充依賴 populateBean

3、initializeBean

假設(shè) bean 是需要 AOP 增強(qiáng)的,那么最終放到緩存中的應(yīng)該是一個(gè)代理 bean。而代理 bean 的產(chǎn)生是在 initializeBean(第三階段) 的時(shí)候。所以,我們推導(dǎo)出:如果只使用一級緩存的話,緩存的插入應(yīng)該放在 initializeBean 之后。

如果在 initializeBean 的時(shí)候記錄緩存,那么碰到循環(huán)依賴的情況,需要在 populateBean(第二階段) 的時(shí)候再去注入循環(huán)依賴的 bean,此時(shí),緩存中是沒有循環(huán)依賴的 bean 的,就會導(dǎo)致 bean 重新創(chuàng)建實(shí)例。

這樣顯然是不行的。

反例:循環(huán)依賴場景:A–>B–>A

A 在 createBeanInstance 時(shí),創(chuàng)建了 bean 實(shí)例,接著 populateBean 會填充依賴的 bean B,從而觸發(fā) B 的加載;

B 在 populateBean 時(shí),發(fā)現(xiàn)要注入依賴的 bean A,先從緩存中獲取 bean A,獲取不到,就會重新創(chuàng)建 bean A。

這樣違背了 bean 的單例性,所以只使用一級緩存是不行的。

理論上使用一級緩存是可以解決普通場景下的循環(huán)依賴的,因?yàn)閷τ谄胀▓鼍?,從始至終 bean 的對象引用始終都是不變的。

但是,如果被循環(huán)依賴的 bean 是一個(gè) AOP 增強(qiáng)的代理 bean 的話,bean 的原始引用和最終產(chǎn)生的 AOP 增強(qiáng) bean 的引用是不一樣的,一級緩存就搞不定了。

疑問:如果在 createBeanInstance 之后就生成代理對象放入一級緩存呢?

我們或許會有疑問,如果不按 spring 原本的設(shè)計(jì),我們在 bean 創(chuàng)建的第一步createBeanInstance 之后就判斷是否生成代理對象,并將要暴露的對象放入一級緩存是不是就可以解決所有場景的循環(huán)依賴問題呢?

分析:

再利用上面的反例來分析一遍,放入一級緩存的 bean 與暴露到容器中的 bean (不管是否有代理)始終是同一個(gè) bean,看似好像是沒有問題的,好像是可以解決循環(huán)依賴的問題。

但是這里忽略了一個(gè)問題:bean 創(chuàng)建的第二步中 populateBean 的底層實(shí)現(xiàn)是將原始 bean 對象包裝成 BeanWrapper,然后通過 BeanWrapper 利用反射設(shè)置值的。

如果在 populateBean 之前生成的是一個(gè)代理對象的話,就帶來了另外一個(gè)問題 :jdk proxy 產(chǎn)生的代理對象是實(shí)現(xiàn)的目標(biāo)類的接口,jdk proxy 的代理類通過 BeanWrapper 去利用反射設(shè)置值時(shí)會因?yàn)檎也坏较鄳?yīng)的屬性或者方法而報(bào)錯(cuò)。

所以,如果在 createBeanInstance 之后就生成代理對象放入一級緩存,也是行不通的。

只用二級緩存會存在什么問題

使用二級緩存也可以分為兩種:使用singletonObjects和earlySingletonObjects,或者使用singletonObjects和singletonFactories。

①使用singletonObjects和earlySingletonObjects

成品放在singletonObjects中,半成品放在earlySingletonObjects中

流程可以這樣走:實(shí)例化A ->將半成品的A放入earlySingletonObjects中 ->填充A的屬性時(shí)發(fā)現(xiàn)取不到B->實(shí)例化B->將半成品的B放入earlySingletonObjects中->從earlySingletonObjects中取出A填充B的屬性->將成品B放入singletonObjects,并從earlySingletonObjects中刪除B->將B填充到A的屬性中->將成品A放入singletonObjects并刪除earlySingletonObjects。

這樣的流程是線程安全的,不過如果A上加個(gè)切面(AOP),這種做法就沒法滿足需求了,因?yàn)閑arlySingletonObjects中存放的都是原始對象,而我們需要注入的其實(shí)是A的代理對象。

②使用singletonObjects和singletonFactories

成品放在singletonObjects中,半成品通過singletonFactories來獲取

流程是這樣的:實(shí)例化A ->創(chuàng)建A的對象工廠并放入singletonFactories中 ->填充A的屬性時(shí)發(fā)現(xiàn)取不到B->實(shí)例化B->創(chuàng)建B的對象工廠并放入singletonFactories中->從singletonFactories中獲取A的對象工廠并獲取A填充到B中->將成品B放入singletonObjects,并從singletonFactories中刪除B的對象工廠->將B填充到A的屬性中->將成品A放入singletonObjects并刪除A的對象工廠。

同樣,這樣的流程也適用于普通的IOC已經(jīng)有并發(fā)的場景,但如果A上加個(gè)切面(AOP)的話,這種情況也無法滿足需求,因?yàn)楫?dāng)A和多個(gè)對象發(fā)生循環(huán)依賴時(shí),其他對象拿到的都是A的不同的代理對象。

疑問:如果在createBeanInstance之后就生成代理對象放入二級緩存呢?

我們思考一種簡單的情況,就以單獨(dú)創(chuàng)建A為例,假設(shè)AB之間現(xiàn)在沒有依賴關(guān)系,但是A被代理了,這個(gè)時(shí)候當(dāng)A完成實(shí)例化后還是會進(jìn)入下面這段代碼:

    // A是單例的,mbd.isSingleton()條件滿足
    // allowCircularReferences:這個(gè)變量代表是否允許循環(huán)依賴,默認(rèn)是開啟的,條件也滿足
    // isSingletonCurrentlyInCreation:正在在創(chuàng)建A,也滿足
    boolean earlySingletonExposure = (mbd.isSingleton() &&this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
    // 所以earlySingletonExposure=true
    // 還是會進(jìn)入到這段代碼中
    if(earlySingletonExposure) {
    // 還是會通過三級緩存提前暴露一個(gè)工廠對象
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

看到了吧,即使沒有循環(huán)依賴,也會將其添加到三級緩存中,而且是不得不添加到三級緩存中,因?yàn)榈侥壳盀橹筍pring也不能確定這個(gè)Bean有沒有跟別的Bean出現(xiàn)循環(huán)依賴。

假設(shè)我們在這里直接使用二級緩存的話,那么意味著所有的Bean在這一步都要完成AOP代理。這樣做有必要嗎?

不僅沒有必要,而且違背了Spring在結(jié)合AOP跟Bean的生命周期的設(shè)計(jì)!Spring結(jié)合AOP跟Bean的生命周期本身就是通過AnnotationAwareAspectJAutoProxyCreator這個(gè)后置處理器來完成的,在這個(gè)后置處理的postProcessAfterInitialization方法中對初始化后的Bean完成AOP代理。如果出現(xiàn)了循環(huán)依賴,那沒有辦法,只有給Bean先創(chuàng)建代理,但是沒有出現(xiàn)循環(huán)依賴的情況下,設(shè)計(jì)之初就是讓Bean在生命周期的最后一步完成代理而不是在實(shí)例化后就立馬完成代理。

Spring 為什么不用二級緩存來解決循環(huán)依賴問題

Spring 原本的設(shè)計(jì)是,bean 的創(chuàng)建過程分三個(gè)階段:

1 創(chuàng)建實(shí)例 createBeanInstance – 創(chuàng)建出 bean 的原始對象

2 填充依賴 populateBean – 利用反射,使用 BeanWrapper 來設(shè)置屬性值

3 initializeBean – 執(zhí)行 bean 創(chuàng)建后的處理,包括 AOP 對象的產(chǎn)生

在沒有循環(huán)依賴的場景下:第 1,2 步都是 bean 的原始對象,第 3 步 initializeBean 時(shí),才會生成 AOP 代理對象。

循環(huán)依賴屬于一個(gè)特殊的場景,如果在第 3 步 initializeBean 時(shí)才去生成 AOP 代理 bean 的話,那么在第 2 步 populateBean 注入循環(huán)依賴 bean 時(shí)就拿不到 AOP 代理 bean 進(jìn)行注入。

所以,循環(huán)依賴打破了 AOP 代理 bean 生成的時(shí)機(jī),需要在 populateBean 之前就生成 AOP 代理 bean。

而且,生成 AOP 代理需要執(zhí)行 BeanPostProcessor,而 Spring 原本的設(shè)計(jì)是在第 3 步 initializeBean 時(shí)才去調(diào)用 BeanPostProcessor 的。

并不是每個(gè) bean 都需要進(jìn)行這樣的處理,所以, Spring 沒有直接在 createBeanInstance 之后直接生成 bean 的早期引用,而是將 bean 的原始對象包裝成了一個(gè) ObjectFactory 放到了三級緩存 Map<String, Object> earlySingletonObjects。

當(dāng)需要用到 bean 的早期引用的時(shí)候,才通過三級緩存 Map<String, ObjectFactory<?>> singletonFactories 來進(jìn)行獲取。

如果只使用二級緩存來解決循環(huán)依賴的話,那么每個(gè) bean 的創(chuàng)建流程中都需要插入一個(gè)流程——創(chuàng)建 bean 的早期引用放入二級緩存。

其實(shí),在真實(shí)的開發(fā)中,絕大部分的情況下都不涉及到循環(huán)依賴,而且 createBeanInstance --> populateBean --> initializeBean 這個(gè)流程也更加符合常理。

所以,猜想 Spring 不用二級緩存來解決循環(huán)依賴問題,是為了保證處理時(shí)清晰明了,bean 的創(chuàng)建就是三個(gè)階段: createBeanInstance --> populateBean --> initializeBean

只有碰到 AOP 代理 bean 被循環(huán)依賴時(shí)的場景,才去特殊處理,提前生成 AOP 代理 bean。

總結(jié)

如果沒有循環(huán)依賴的情況的話,一級緩存就可以搞定所有的情況,只需要在 bean 完全初始化好之后將其放入一級緩存即可。

但是一級緩存解決不了循環(huán)依賴的情況,所以,Spring 使用三級緩存來解決了循環(huán)依賴問題。如果使用二級緩存的話,理論上是可行的,但是 Spring 選擇了三級緩存來實(shí)現(xiàn),讓 bean 的創(chuàng)建流程更加符合常理,更加清晰明了。

到此這篇關(guān)于Spring詳細(xì)講解循環(huán)依賴是什么的文章就介紹到這了,更多相關(guān)Spring循環(huán)依賴內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java 設(shè)計(jì)模式之單例的實(shí)例詳解

    java 設(shè)計(jì)模式之單例的實(shí)例詳解

    這篇文章主要介紹了java 設(shè)計(jì)模式之單例的實(shí)例詳解的相關(guān)資料, 希望通過本文能幫助到大家,讓大家徹底理解掌握單例模式,需要的朋友可以參考下
    2017-09-09
  • spring boot 1.5.4 集成shiro+cas,實(shí)現(xiàn)單點(diǎn)登錄和權(quán)限控制

    spring boot 1.5.4 集成shiro+cas,實(shí)現(xiàn)單點(diǎn)登錄和權(quán)限控制

    這篇文章主要介紹了spring boot 1.5.4 集成shiro+cas,實(shí)現(xiàn)單點(diǎn)登錄和權(quán)限控制,需要的朋友可以參考下
    2017-06-06
  • Java集合總結(jié)

    Java集合總結(jié)

    今天小編就為大家分享一篇關(guān)于Java集合總結(jié),小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • 微信小程序錄音文件格式silk遇到的問題及解決方法

    微信小程序錄音文件格式silk遇到的問題及解決方法

    錄音文件為silk格式,說是silk其實(shí)是base64加密后的webm格式,只需將其轉(zhuǎn)為webm格式即可。但是在處理過程中遇到各種坑,下面小編給大家?guī)砹宋⑿判〕绦蜾浺粑募袷絪ilk遇到的問題及解決方法,感興趣的朋友一起看看吧
    2018-09-09
  • java冷知識:javac AbstractProcessor詳解

    java冷知識:javac AbstractProcessor詳解

    這篇文章主要介紹了java冷知識:javac AbstractProcessor詳解,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Springboot開發(fā)OAuth2認(rèn)證授權(quán)與資源服務(wù)器操作

    Springboot開發(fā)OAuth2認(rèn)證授權(quán)與資源服務(wù)器操作

    這篇文章主要介紹了Springboot開發(fā)OAuth2認(rèn)證授權(quán)與資源服務(wù)器操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Mybatis?sqlMapConfig.xml中的mappers標(biāo)簽使用

    Mybatis?sqlMapConfig.xml中的mappers標(biāo)簽使用

    這篇文章主要介紹了Mybatis?sqlMapConfig.xml中的mappers標(biāo)簽使用方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
    2022-01-01
  • Spring security實(shí)現(xiàn)記住我下次自動登錄功能過程詳解

    Spring security實(shí)現(xiàn)記住我下次自動登錄功能過程詳解

    這篇文章主要介紹了Spring security實(shí)現(xiàn)記住我下次自動登錄功能過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • 淺析Java中的虛擬線程

    淺析Java中的虛擬線程

    在本篇文章中,小編將帶大家深入了解Java虛擬線程的原理、如何使用、使用的注意事項(xiàng)以及其他相似技術(shù)的差別,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-10-10
  • Java二維數(shù)組實(shí)現(xiàn)數(shù)字拼圖效果

    Java二維數(shù)組實(shí)現(xiàn)數(shù)字拼圖效果

    這篇文章主要為大家詳細(xì)介紹了Java二維數(shù)組實(shí)現(xiàn)數(shù)字拼圖效果,控制臺可以對空格進(jìn)行移動,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-07-07

最新評論