日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

Spring 如何解決循環(huán)依賴,網(wǎng)上的資料很多,但是感覺寫得好的極少,特別是源碼解讀方面,我就自己單獨(dú)出一篇,這篇文章絕對(duì)肝!

不 BB,上文章目錄。

Spring 如何解決循環(huán)依賴?圖片

1. 基礎(chǔ)知識(shí)

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

一個(gè)或多個(gè)對(duì)象之間存在直接或間接的依賴關(guān)系,這種依賴關(guān)系構(gòu)成一個(gè)環(huán)形調(diào)用,有下面 3 種方式。

Spring 如何解決循環(huán)依賴?圖片

我們看一個(gè)簡單的 Demo,對(duì)標(biāo)“情況 2”。

@Service
public class LouzAI1 {

    @Autowired
    private Louzai2 louzai2;

    public void test1() {
    }
}

@Service
public class Louzai2 {
    @Autowired
    private Louzai1 louzai1;

    public void test2() {
    }
}

這是一個(gè)經(jīng)典的循環(huán)依賴,它能正常運(yùn)行,后面我們會(huì)通過源碼的角度,解讀整體的執(zhí)行流程。

1.2 三級(jí)緩存

解讀源碼流程之前,spring 內(nèi)部的三級(jí)緩存邏輯必須了解,要不然后面看代碼會(huì)蒙圈。

  • 第一級(jí)緩存:singletonObjects,用于保存實(shí)例化、注入、初始化完成的 bean 實(shí)例;
  • 第二級(jí)緩存:earlySingletonObjects,用于保存實(shí)例化完成的 bean 實(shí)例;
  • 第三級(jí)緩存:singletonFactories,用于保存 bean 創(chuàng)建工廠,以便后面有機(jī)會(huì)創(chuàng)建代理對(duì)象。

這是最核心,我們直接上源碼:

Spring 如何解決循環(huán)依賴?圖片

執(zhí)行邏輯:

  • 先從“第一級(jí)緩存”找對(duì)象,有就返回,沒有就找“二級(jí)緩存”;
  • 找“二級(jí)緩存”,有就返回,沒有就找“三級(jí)緩存”;
  • 找“三級(jí)緩存”,找到了,就獲取對(duì)象,放到“二級(jí)緩存”,從“三級(jí)緩存”移除。

1.3 原理執(zhí)行流程

我把“情況 2”執(zhí)行的流程分解為下面 3 步,是不是和“套娃”很像 ?

Spring 如何解決循環(huán)依賴?圖片

整個(gè)執(zhí)行邏輯如下:

  1. 在第一層中,先去獲取 A 的 Bean,發(fā)現(xiàn)沒有就準(zhǔn)備去創(chuàng)建一個(gè),然后將 A 的代理工廠放入“三級(jí)緩存”(這個(gè) A 其實(shí)是一個(gè)半成品,還沒有對(duì)里面的屬性進(jìn)行注入),但是 A 依賴 B 的創(chuàng)建,就必須先去創(chuàng)建 B;
  2. 在第二層中,準(zhǔn)備創(chuàng)建 B,發(fā)現(xiàn) B 又依賴 A,需要先去創(chuàng)建 A;
  3. 在第三層中,去創(chuàng)建 A,因?yàn)榈谝粚右呀?jīng)創(chuàng)建了 A 的代理工廠,直接從“三級(jí)緩存”中拿到 A 的代理工廠,獲取 A 的代理對(duì)象,放入“二級(jí)緩存”,并清除“三級(jí)緩存”;
  4. 回到第二層,現(xiàn)在有了 A 的代理對(duì)象,對(duì) A 的依賴完美解決(這里的 A 仍然是個(gè)半成品),B 初始化成功;
  5. 回到第一層,現(xiàn)在 B 初始化成功,完成 A 對(duì)象的屬性注入,然后再填充 A 的其它屬性,以及 A 的其它步驟(包括 AOP),完成對(duì) A 完整的初始化功能(這里的 A 才是完整的 Bean)。
  6. 將 A 放入“一級(jí)緩存”。

為什么要用 3 級(jí)緩存 ?我們先看源碼執(zhí)行流程,后面我會(huì)給出答案。

2. 源碼解讀

注意:Spring 的版本是 5.2.15.RELEASE,否則和我的代碼不一樣?。?!

上面的知識(shí),網(wǎng)上其實(shí)都有,下面才是我們的重頭戲,讓你跟著樓仔,走一遍代碼流程。

2.1 代碼入口

Spring 如何解決循環(huán)依賴?圖片

Spring 如何解決循環(huán)依賴?圖片

這里需要多跑幾次,把前面的 beanName 跳過去,只看 louzai1。

Spring 如何解決循環(huán)依賴?圖片

Spring 如何解決循環(huán)依賴?圖片

2.2 第一層

Spring 如何解決循環(huán)依賴?圖片

進(jìn)入 doGetBean(),從 getSingleton() 沒有找到對(duì)象,進(jìn)入創(chuàng)建 Bean 的邏輯。

Spring 如何解決循環(huán)依賴?圖片

Spring 如何解決循環(huán)依賴?圖片

進(jìn)入 doCreateBean() 后,調(diào)用 addSingletonFactory()。

Spring 如何解決循環(huán)依賴?圖片

往三級(jí)緩存 singletonFactories 塞入 louzai1 的工廠對(duì)象。

Spring 如何解決循環(huán)依賴?圖片

Spring 如何解決循環(huán)依賴?圖片

進(jìn)入到 populateBean(),執(zhí)行 postProcessProperties(),這里是一個(gè)策略模式,找到下圖的策略對(duì)象。

Spring 如何解決循環(huán)依賴?圖片

正式進(jìn)入該策略對(duì)應(yīng)的方法。

Spring 如何解決循環(huán)依賴?圖片

下面都是為了獲取 louzai1 的成員對(duì)象,然后進(jìn)行注入。

Spring 如何解決循環(huán)依賴?圖片

Spring 如何解決循環(huán)依賴?圖片

Spring 如何解決循環(huán)依賴?圖片

Spring 如何解決循環(huán)依賴?圖片

進(jìn)入 doResolveDependency(),找到 louzai1 依賴的對(duì)象名 louzai2

Spring 如何解決循環(huán)依賴?圖片

需要獲取 louzai2 的 bean,是 AbstractBeanFactory 的方法。

Spring 如何解決循環(huán)依賴?圖片

正式獲取 louzai2 的 bean。

Spring 如何解決循環(huán)依賴?圖片

到這里,第一層套娃基本結(jié)束,因?yàn)?louzai1 依賴 louzai2,下面我們進(jìn)入第二層套娃。

2.3 第二層

Spring 如何解決循環(huán)依賴?圖片

獲取 louzai2 的 bean,從 doGetBean(),到 doResolveDependency(),和第一層的邏輯完全一樣,找到 louzai2 依賴的對(duì)象名 louzai1。

前面的流程全部省略,直接到 doResolveDependency()。

Spring 如何解決循環(huán)依賴?圖片

正式獲取 louzai1 的 bean。

Spring 如何解決循環(huán)依賴?圖片

到這里,第二層套娃結(jié)束,因?yàn)?louzai2 依賴 louzai1,所以我們進(jìn)入第三層套娃。

2.4 第三層

Spring 如何解決循環(huán)依賴?圖片

獲取 louzai1 的 bean,在第一層和第二層中,我們每次都會(huì)從 getSingleton() 獲取對(duì)象,但是由于之前沒有初始化 louzai1 和 louzai2 的三級(jí)緩存,所以獲取對(duì)象為空。

Spring 如何解決循環(huán)依賴?圖片

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

敲重點(diǎn)!敲重點(diǎn)??!敲重點(diǎn)!??!

到了第三層,由于第三級(jí)緩存有 louzai1 數(shù)據(jù),這里使用三級(jí)緩存中的工廠,為 louzai1 創(chuàng)建一個(gè)代理對(duì)象,塞入二級(jí)緩存。

Spring 如何解決循環(huán)依賴?圖片

這里就拿到了 louzai1 的代理對(duì)象,解決了 louzai2 的依賴關(guān)系,返回到第二層。

2.5 返回第二層

返回第二層后,louzai2 初始化結(jié)束,這里就結(jié)束了么?二級(jí)緩存的數(shù)據(jù),啥時(shí)候會(huì)給到一級(jí)呢?

甭著急,看這里,還記得在 doGetBean() 中,我們會(huì)通過 createBean() 創(chuàng)建一個(gè) louzai2 的 bean,當(dāng) louzai2 的 bean 創(chuàng)建成功后,我們會(huì)執(zhí)行 getSingleton(),它會(huì)對(duì) louzai2 的結(jié)果進(jìn)行處理。

Spring 如何解決循環(huán)依賴?圖片

我們進(jìn)入 getSingleton(),會(huì)看到下面這個(gè)方法。

Spring 如何解決循環(huán)依賴?圖片

這里就是處理 louzai2 的 一、二級(jí)緩存的邏輯,將二級(jí)緩存清除,放入一級(jí)緩存。

Spring 如何解決循環(huán)依賴?圖片

2.6 返回第一層

同 2.5,louzai1 初始化完畢后,會(huì)把 louzai1 的二級(jí)緩存清除,將對(duì)象放入一級(jí)緩存。

Spring 如何解決循環(huán)依賴?圖片

到這里,所有的流程結(jié)束,我們返回 louzai1 對(duì)象。

3. 原理深度解讀

3.1 什么要有 3 級(jí)緩存 ?

這是一道非常經(jīng)典的面試題,前面已經(jīng)告訴大家詳細(xì)的執(zhí)行流程,包括源碼解讀,但是沒有告訴大家為什么要用 3 級(jí)緩存?

這里是重點(diǎn)!敲黑板!??!

我們先說“一級(jí)緩存”的作用,變量命名為 singletonObjects,結(jié)構(gòu)是 Map<String, Object>,它就是一個(gè)單例池,將初始化好的對(duì)象放到里面,給其它線程使用,如果沒有第一級(jí)緩存,程序不能保證 Spring 的單例屬性。

“二級(jí)緩存”先放放,我們直接看“三級(jí)緩存”的作用,變量命名為 singletonFactories,結(jié)構(gòu)是 Map<String, ObjectFactory<?>>,Map 的 Value 是一個(gè)對(duì)象的代理工廠,所以“三級(jí)緩存”的作用,其實(shí)就是用來存放對(duì)象的代理工廠。

那這個(gè)對(duì)象的代理工廠有什么作用呢,我先給出答案,它的主要作用是存放半成品的單例 Bean,目的是為了“打破循環(huán)”,可能大家還是不太懂,這里我再稍微解釋一下。

我們回到文章開頭的例子,創(chuàng)建 A 對(duì)象時(shí),會(huì)把實(shí)例化的 A 對(duì)象存入“三級(jí)緩存”,這個(gè) A 其實(shí)是個(gè)半成品,因?yàn)闆]有完成依賴屬性 B 的注入,所以后面當(dāng)初始化 B 時(shí),B 又要去找 A,這時(shí)就需要從“三級(jí)緩存”中拿到這個(gè)半成品的 A(這里描述,其實(shí)也不完全準(zhǔn)確,因?yàn)椴皇侵苯幽?,為了讓大家好理解,我就先這樣描述),打破循環(huán)。

那我再問一個(gè)問題,為什么“三級(jí)緩存”不直接存半成品的 A,而是要存一個(gè)代理工廠呢 ?答案是因?yàn)?AOP。

在解釋這個(gè)問題前,我們看一下這個(gè)代理工廠的源碼,讓大家有一個(gè)更清晰的認(rèn)識(shí)。

直接找到創(chuàng)建 A 對(duì)象時(shí),把實(shí)例化的 A 對(duì)象存入“三級(jí)緩存”的代碼,直接用前面的兩幅截圖。

Spring 如何解決循環(huán)依賴?圖片

Spring 如何解決循環(huán)依賴?圖片

下面我們主要看這個(gè)對(duì)象工廠是如何得到的,進(jìn)入 getEarlyBeanReference() 方法。

Spring 如何解決循環(huán)依賴?圖片

Spring 如何解決循環(huán)依賴?圖片

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

Spring 如何解決循環(huán)依賴?圖片

最后一幅圖太重要了,我們知道這個(gè)對(duì)象工廠的作用:

  • 如果 A 有 AOP,就創(chuàng)建一個(gè)代理對(duì)象;
  • 如果 A 沒有 AOP,就返回原對(duì)象。

那“二級(jí)緩存”的作用就清楚了,就是用來存放對(duì)象工廠生成的對(duì)象,這個(gè)對(duì)象可能是原對(duì)象,也可能是個(gè)代理對(duì)象。

我再問一個(gè)問題,為什么要這樣設(shè)計(jì)呢?把二級(jí)緩存干掉不行么 ?我們繼續(xù)往下看。

3.2 能干掉第 2 級(jí)緩存么 ?

@Service
public class A {

    @Autowired
    private B b;

    @Autowired
    private C c;

    public void test1() {
    }
}

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

    public void test2() {
    }
}

@Service
public class C {

    @Autowired
    private A a;

    public void test3() {
    }
}

根據(jù)上面的套娃邏輯,A 需要找 B 和 C,但是 B 需要找 A,C 也需要找 A。

假如 A 需要進(jìn)行 AOP,因?yàn)榇韺?duì)象每次都是生成不同的對(duì)象,如果干掉第二級(jí)緩存,只有第一、三級(jí)緩存:

  • B 找到 A 時(shí),直接通過三級(jí)緩存的工廠的代理對(duì)象,生成對(duì)象 A1。
  • C 找到 A 時(shí),直接通過三級(jí)緩存的工廠的代理對(duì)象,生成對(duì)象 A2。

看到問題沒?你通過 A 的工廠的代理對(duì)象,生成了兩個(gè)不同的對(duì)象 A1 和 A2,所以為了避免這種問題的出現(xiàn),我們搞個(gè)二級(jí)緩存,把 A1 存下來,下次再獲取時(shí),直接從二級(jí)緩存獲取,無需再生成新的代理對(duì)象。

所以“二級(jí)緩存”的目的是為了避免因?yàn)?AOP 創(chuàng)建多個(gè)對(duì)象,其中存儲(chǔ)的是半成品的 AOP 的單例 bean。

如果沒有 AOP 的話,我們其實(shí)只要 1、3 級(jí)緩存,就可以滿足要求。

4. 寫在最后

我們再回顧一下 3 級(jí)緩存的作用:

  • 一級(jí)緩存:為“Spring 的單例屬性”而生,就是個(gè)單例池,用來存放已經(jīng)初始化完成的單例 Bean;
  • 二級(jí)緩存:為“解決 AOP”而生,存放的是半成品的 AOP 的單例 Bean;
  • 三級(jí)緩存:為“打破循環(huán)”而生,存放的是生成半成品單例 Bean 的工廠方法。

如果你能理解上面我說的三條,恭喜你,你對(duì) Spring 的循環(huán)依賴?yán)斫獾梅浅M笍兀?/p>

關(guān)于循環(huán)依賴的知識(shí),其實(shí)還有,因?yàn)槠颍揖筒辉賹懥?,這篇文章的重點(diǎn),一方面是告訴大家循環(huán)依賴的核心原理,另一方面是讓大家自己去 debug 代碼,跑跑流程,挺有意思的。

可能有同學(xué)會(huì)問 “樓哥,你之前是不是經(jīng)??丛创a,然后這個(gè)流程,你是不是 debug 了很久?”

我之前其實(shí)沒怎么看過開源代碼,這個(gè)流程,前期理論知識(shí)看了 2.5 個(gè)小時(shí),然后 debug 4.5 小時(shí),就基本全部走通了,最難的地方,就是三層套娃,稍微有些繞。

這里也簡單說一下我看源碼的心得:

  1. 需要掌握基本的設(shè)計(jì)模式;
  2. 看源碼前,最好能找一些理論知識(shí)先看看;
  3. 學(xué)會(huì)讀英文注釋,不會(huì)的話就百度翻譯;
  4. debug 時(shí),要克制自己,不要陷入無用的細(xì)節(jié),這個(gè)最重要。

其中最難的是第 4 步,因?yàn)楹芏嗤瑢W(xué)看 Spring 源碼,每看一個(gè)方法,就想多研究研究,這樣很容易被繞進(jìn)去了,這個(gè)要學(xué)會(huì)克制,有大局觀,并能分辨哪里是核心邏輯,至于如何分辨,可以在網(wǎng)上先找些資料,如果沒有的話,就只能多看代碼了。

分享到:
標(biāo)簽:Spring
用戶無頭像

網(wǎng)友整理

注冊時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評(píng)定2018-06-03

通用課目體育訓(xùn)練成績評(píng)定