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

Spring三級緩存解決循環(huán)依賴的過程分析

 更新時間:2023年04月03日 09:16:57   作者:我叫小八  
這篇文章主要介紹了Spring三級緩存解決循環(huán)依賴,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下

循環(huán)依賴

什么是循環(huán)依賴?很簡單,看下方的代碼就知曉了

@Service
public class A {
    @Autowired
    private B b;
}

@Service
public class B {
    @Autowired
    private A a;
}

上面這兩種方式都是循環(huán)依賴,應(yīng)該很好理解,當(dāng)然也可以是三個 Bean 甚至更多的 Bean 相互依賴,原理都是一樣的,今天我們主要分析兩個 Bean 的依賴。

在這里插入圖片描述

這種循環(huán)依賴可能會產(chǎn)生問題,例如 A 要依賴 B,發(fā)現(xiàn) B 還沒創(chuàng)建。
于是開始創(chuàng)建 B ,創(chuàng)建的過程發(fā)現(xiàn) B 要依賴 A, 而 A 還沒創(chuàng)建好呀,因為它要等 B 創(chuàng)建好。
就這樣它們倆就擱這卡 bug 了。

解決思路

上面這種循環(huán)依賴在實際場景中是會出現(xiàn)的,所以 Spring 需要解決這個問題,那如何解決呢?
關(guān)鍵就是提前暴露未完全創(chuàng)建完畢的 Bean。
在 Spring 中,只有同時滿足以下兩點才能解決循環(huán)依賴的問題:

依賴的 Bean 必須都是單例依賴注入的方式,必須不全是構(gòu)造器注入,且 beanName 字母序在前的不能是構(gòu)造器注入

Spring 只支持單例的循環(huán)依賴,因為如果兩個 Bean 都是原型模式的話:
創(chuàng)建 A1 需要創(chuàng)建一個 B1。
創(chuàng)建 B1 的時候要創(chuàng)建一個 A2。
創(chuàng)建 A2 又要創(chuàng)建一個 B2。
創(chuàng)建 B2 又要創(chuàng)建一個 A3。
創(chuàng)建 A3 又要創(chuàng)建一個 B3…
就又卡 BUG 了,是吧,因為原型模式都需要創(chuàng)建新的對象,不能跟用以前的對象。
如果是單例的話,創(chuàng)建 A 需要創(chuàng)建 B,而創(chuàng)建的 B 需要的是之前的個 A, 不然就不叫單例了,對吧?
也是基于這點, Spring 就能操作操作了。
具體做法就是:先創(chuàng)建 A,此時的 A 是不完整的(沒有注入 B),用個 map 保存這個不完整的 A,再創(chuàng)建 B ,B 需要 A。
所以從那個 map 得到“不完整”的 A,此時的 B 就完整了,然后 A 就可以注入 B,然后 A 就完整了,B 也完整了,且它們是相互依賴的。

那為啥必須不全是構(gòu)造器注入,因為在 Spring 中創(chuàng)建 Bean 分三步:

  • 實例化,createBeanInstance,就是 new 了個對象
  • 屬性注入,populateBean, 就是 set 一些屬性值
  • 初始化,initializeBean,執(zhí)行一些 aware 接口中的方法,initMethod,AOP代理等

明確了上面這三點,再結(jié)合我上面說的“不完整的”,我們來理一下。
如果全是構(gòu)造器注入,比如A(B b),那表明在 new 的時候,就需要得到 B,此時需要 new B 。
但是 B 也是要在構(gòu)造的時候注入 A ,即B(A a),這時候 B 需要在一個 map 中找到不完整的 A ,發(fā)現(xiàn)找不到。
為什么找不到?因為 A 還沒 new 完呢,所以找到不完整的 A,因此如果全是構(gòu)造器注入的話,那么 Spring 無法處理循環(huán)依賴。

解決流程

經(jīng)過上面的鋪墊,我想你對 Spring 如何解決循環(huán)依賴應(yīng)該已經(jīng)有點感覺了,接下來我們就來看看它到底是如何實現(xiàn)的。
明確了 Spring 創(chuàng)建 Bean 的三步驟之后,我們再來看看它為單例搞的三個 map:

  • 一級緩存,singletonObjects,存儲所有已創(chuàng)建完畢的單例 Bean (完整的 Bean)
  • 二級緩存,earlySingletonObjects,存儲所有僅完成實例化,但還未進(jìn)行屬性注入和初始化的 Bean
  • 三級緩存,singletonFactories,存儲能建立這個 Bean 的一個工廠,通過工廠能獲取這個 Bean,延遲化 Bean 的生成,工廠生成的 Bean 會塞入二級緩存

在這里插入圖片描述

這三個 map 是如何配合的呢?

  • 首先,獲取單例 Bean 的時候會通過 BeanName 先去 singletonObjects(一級緩存) 查找完整的 Bean,如果找到則直接返回,否則進(jìn)行步驟 2。
  • 看對應(yīng)的 Bean 是否在創(chuàng)建中,如果不在直接返回找不到,如果是,則會去 earlySingletonObjects (二級緩存)查找 Bean,如果找到則返回,否則進(jìn)行步驟 3
  • 去 singletonFactories (三級緩存)通過 BeanName 查找到對應(yīng)的工廠,如果存著工廠則通過工廠創(chuàng)建 Bean ,并且放置到 earlySingletonObjects 中。
  • 如果三個緩存都沒找到,則返回 null。

從上面的步驟我們可以得知,如果查詢發(fā)現(xiàn) Bean 還未創(chuàng)建,到第二步就直接返回 null,不會繼續(xù)查二級和三級緩存。
返回 null 之后,說明這個 Bean 還未創(chuàng)建,這個時候會標(biāo)記這個 Bean 正在創(chuàng)建中,然后再調(diào)用 createBean 來創(chuàng)建 Bean,而實際創(chuàng)建是調(diào)用方法 doCreateBean。
doCreateBean 這個方法就會執(zhí)行上面我們說的三步驟:

  • 實例化
  • 屬性注入
  • 初始化

以上面的例子來講解,在實例化A 之后,會往三級緩存 singletonFactories 塞入一個工廠A,而調(diào)用這個工廠A的 getObject 方法,就能得到這個 A。
要注意,此時 Spring 是不知道會不會有循環(huán)依賴發(fā)生的,但是它不管,反正往 singletonFactories 塞這個工廠,這里就是提前暴露。
然后就開始執(zhí)行屬性注入,這個時候 A 發(fā)現(xiàn)需要注入 B,所以去 getBean(B),此時又會走一遍上面描述的邏輯,到了 B 的屬性注入這一步。
此時 B 調(diào)用 getBean(A),這時候一級緩存里面找不到,但是發(fā)現(xiàn) A 正在創(chuàng)建中的,于是去二級緩存找,發(fā)現(xiàn)沒找到,于是去三級緩存找,然后找到了。
并且通過上面提前在三級緩存里暴露的工廠得到 A,然后將這個工廠從三級緩存里刪除,并將 A 加入到二級緩存中。
然后結(jié)果就是 B 屬性注入成功。
緊接著 B 調(diào)用 initializeBean 初始化,最終返回,此時 B 已經(jīng)被加到了一級緩存里 。
這時候就回到了 A 的屬性注入,此時注入了 B,接著執(zhí)行初始化,最后 A 也會被加到一級緩存里,且從二級緩存中刪除 A。
Spring 解決依賴循環(huán)就是按照上面所述的邏輯來實現(xiàn)的。
重點就是在對象實例化之后,都會在三級緩存里加入一個工廠,提前對外暴露還未完整的 Bean,這樣如果被循環(huán)依賴了,對方就可以利用這個工廠得到一個不完整的 Bean,破壞了循環(huán)的條件。

二個緩存不行?

上面都說了那么多了,那我們思考下,解決循環(huán)依賴需要三級緩存嗎?
很明顯,如果僅僅只是為了破解循環(huán)依賴,二個緩存夠了,壓根就不必要三級。
你思考一下,在實例化 Bean A 之后,我在二級 map 里面塞入這個 A,然后繼續(xù)屬性注入。
發(fā)現(xiàn) A 依賴 B 所以要創(chuàng)建 Bean B,這時候 B 就能從二級 map 得到 A ,完成 B 的建立之后, A 自然而然能完成。
所以為什么要搞個三級緩存,且里面存的是創(chuàng)建 Bean 的工廠呢?
我們來看下調(diào)用工廠的 getObject 到底會做什么,實際會調(diào)用下面這個方法:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

重點就在中間的判斷,如果 false,返回就是參數(shù)傳進(jìn)來的 bean,沒任何變化。
如果是 true 說明有 InstantiationAwareBeanPostProcessors 。
且循環(huán)的是 smartInstantiationAware 類型,如有這個 BeanPostProcessor 說明 Bean 需要被 aop 代理。
我們都知道如果有代理的話,那么我們想要直接拿到的是代理對象。
也就是說如果 A 需要被代理,那么 B 依賴的 A 是已經(jīng)被代理的 A,所以我們不能返回 A 給 B,而是返回代理的 A 給 B。
這個工廠的作用就是判斷這個對象是否需要代理,如果否則直接返回,如果是則返回代理對象。
看到這明白的小伙伴肯定會問,那跟三級緩存有什么關(guān)系,我可以在要放到二級緩存的時候判斷這個 Bean 是否需要代理,如果要直接放代理的對象不就完事兒了。
是的,這個思路看起來沒任何問題,問題就出在時機(jī),這跟 Bean 的生命周期有關(guān)系。
Spring 原本的設(shè)計是,bean 的創(chuàng)建過程分三個階段:
1 創(chuàng)建實例 createBeanInstance – 創(chuàng)建出 bean 的原始對象
2 填充依賴 populateBean – 利用反射,使用 BeanWrapper 來設(shè)置屬性值
3 initializeBean – 執(zhí)行 bean 創(chuàng)建后的處理,包括 AOP 對象的產(chǎn)生
在沒有循環(huán)依賴的場景下:第 1,2 步都是 bean 的原始對象,第 3 步 initializeBean 時,才會生成 AOP 代理對象。
所以,循環(huán)依賴打破了 AOP 代理 bean 生成的時機(jī),需要在 populateBean 之前就生成 AOP 代理 bean。
而且,生成 AOP 代理需要執(zhí)行 BeanPostProcessor,而 Spring 原本的設(shè)計是在第 3 步 initializeBean 時才去調(diào)用 BeanPostProcessor 的。
所以 Spring 先在一個三級緩存放置一個工廠,用于代表Bean的引用。只有在上面的第三步時,才會通過這個工廠去創(chuàng)建代理對象,這樣生命周期就不會亂套了。

理論上來說,使用二級緩存是可以解決 AOP 代理 bean 的循環(huán)依賴的。只是 Spring 沒有選擇這樣去實現(xiàn)。Spring 選擇了三級緩存來實現(xiàn),讓 bean 的創(chuàng)建流程更加符合常理,更加清晰明了。

總結(jié)

好了,看到這里想必你應(yīng)該對 Spring 的循環(huán)依賴很清晰了,并且面試的時候肯定也難不倒你了。
我稍微總結(jié)下:

  • 有構(gòu)造器注入,不一定會產(chǎn)生問題,具體得看是否都是構(gòu)造器注和 BeanName 的字母序
  • 如果單純?yōu)榱舜蚱蒲h(huán)依賴,不需要三級緩存,兩級就夠了。
  • 三級緩存是否為延遲代理的創(chuàng)建,盡量不打破 Bean 的生命周期

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

相關(guān)文章

  • Java中數(shù)字黑洞實現(xiàn)代碼

    Java中數(shù)字黑洞實現(xiàn)代碼

    這篇文章主要介紹了Java編程中如何實現(xiàn)數(shù)字黑洞算法游戲,其中涉及到了數(shù)組、scanner、if語句等Java編程的基礎(chǔ)知識,需要的朋友可以參考下
    2017-09-09
  • mybatis plus開發(fā)過程中遇到的問題記錄及解決

    mybatis plus開發(fā)過程中遇到的問題記錄及解決

    這篇文章主要介紹了mybatis plus開發(fā)過程中遇到的問題記錄及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • 解決IDEA中不能正常輸入光標(biāo)變粗的問題

    解決IDEA中不能正常輸入光標(biāo)變粗的問題

    這篇文章主要介紹了在IDEA中不能正常輸入光標(biāo)變粗的解決方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2020-09-09
  • java字符串的截取方法substring()代碼解析

    java字符串的截取方法substring()代碼解析

    這篇文章主要介紹了java字符串的截取方法substring()代碼解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-02-02
  • java導(dǎo)出csv格式文件的方法

    java導(dǎo)出csv格式文件的方法

    這篇文章主要為大家詳細(xì)介紹了java導(dǎo)出csv格式文件的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-12-12
  • Java使用FileReader讀取文件詳解

    Java使用FileReader讀取文件詳解

    本文將為大家介紹FileReader類的基本用法,包括如何創(chuàng)建FileReader對象,如何讀取文件,以及如何關(guān)閉流,感興趣的小伙伴可以跟隨小編一起了解一下
    2023-09-09
  • Java Chassis3注冊中心分區(qū)隔離技術(shù)解密

    Java Chassis3注冊中心分區(qū)隔離技術(shù)解密

    這篇文章主要為大家介紹了Java Chassis3注冊中心分區(qū)隔離技術(shù)解密,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • Mybatis-plus與Mybatis依賴沖突問題解決方法

    Mybatis-plus與Mybatis依賴沖突問題解決方法

    ,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧這篇文章主要介紹了Mybatis-plus與Mybatis依賴沖突問題解決方法
    2021-04-04
  • Java中StringUtils工具類進(jìn)行String為空的判斷解析

    Java中StringUtils工具類進(jìn)行String為空的判斷解析

    這篇文章主要介紹了Java中StringUtils工具類進(jìn)行String為空的判斷解析,具有一定借鑒價值,需要的朋友可以參考下
    2018-01-01
  • Java生成及校驗token的實踐

    Java生成及校驗token的實踐

    Token 的生成和校驗機(jī)制為應(yīng)用程序提供了一種安全的身份驗證和授權(quán)方式,可以用于用戶認(rèn)證、API 訪問控制等場景,本文主要介紹了Java生成及校驗token的實踐,具有一定的參考價值,感興趣的可以了解一下
    2024-04-04

最新評論