成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

這次徹底搞懂Spring循環(huán)依賴

開發(fā) 前端
今天我們系統(tǒng)分析了 Spring 為開發(fā)人員提供的循環(huán)依賴解決方案。雖然基于 Spring 框架實現(xiàn) Bean 的依賴注入比較簡單,但也存在一些最佳實踐,尤其是在使用 Spring 的過程中,經(jīng)常碰到的循環(huán)依賴問題,需要開發(fā)人員對框架的底層原理有一定的了解。

今天我們討論 Spring 框架所提供的核心功能之一:依賴注入。

依賴注入可以說是使用 Spring 框架的基本手段,我們通過它獲取所需的各種 Bean。但在使用不同的依賴注入類型時,經(jīng)常會碰到循環(huán)依賴問題。為了深入分析這一問題的解決方案,我們先從 Spring 依賴注入的類型和循環(huán)依賴的基本概念開始講起。

Spring 依賴注入和循環(huán)依賴

Spring 為開發(fā)人員提供了三種不同的依賴注入類型,分別是字段注入、構(gòu)造器注入和 Setter 方法注入。

Spring 框架的三種依賴注入類型Spring 框架的三種依賴注入類型

其中,字段注入是最常用、也是最容易使用的一種。但是,它也是三種注入方式中最應(yīng)該避免使用的。因為它可能導(dǎo)致潛在的循環(huán)依賴。所謂循環(huán)依賴,就是兩個類之間互相注入,例如這段示例代碼:

public class ClassA {
    @Autowired
    private ClassB classB;
}
public class ClassB {
    @Autowired
    private ClassA classA;
}

顯然,這里的 ClassA 和 ClassB 通過@Autowired 注解相互注入,發(fā)生了循環(huán)依賴。在 Spring 中,上述代碼是合法的,容器啟動時并不會報任何錯誤,只有在使用到具體某個 ClassA 或 ClassB 時,才會報錯。

事實上,Spring 官方也不推薦開發(fā)人員使用字段注入這種注入模式,而是推薦構(gòu)造器注入。基于構(gòu)造器注入,前面介紹的 ClassA 和 ClassB 之間的循環(huán)依賴關(guān)系是這樣的:

public class ClassA {
 private ClassB classB;
 @Autowired
    public ClassA(ClassB classB) {
        this.classB = classB;
    }
}
public class ClassB {
  private ClassA classA;
 @Autowired
    public ClassB(ClassA classA) {
        this.classA = classA;
    }
}

這時候,如果啟動 Spring 容器,就會拋出一個循環(huán)依賴異常,提醒你應(yīng)該避免循環(huán)依賴。

其實,Setter 方法注入可以很好地解決循環(huán)依賴問題,如下所示的代碼是可以正確執(zhí)行的:

public class ClassA {
 private ClassB classB;
 @Autowired
    public void setClassB(ClassB classB) {
        this.classB = classB;
    }
}
public class ClassB {
   private ClassA classA;
 @Autowired
    public void setClassA(ClassA classA) {
        this.classA = classA;
    }
}

請注意,上述代碼能夠正確執(zhí)行的前提是:ClassA 和 ClassB 的作用域都是“Singleton”,即單例。所謂的單例,指的就是不管對某一個類的引用有多少個,容器只會創(chuàng)建該類的一個實例。

講到這里,你可能會好奇,這就需要剖析 Spring 中對于單例 Bean 的存儲和獲取方式,讓我們一起來看一下。

Spring 循環(huán)依賴解決方案

對于單例作用域來說,在 Spring 容器的整個生命周期內(nèi),有且僅有一個 Bean 對象,所以很容易想到這個對象應(yīng)該位于緩存中。Spring 為了解決單例 Bean 的循環(huán)依賴問題,使用了三級緩存。這是 Spring 在設(shè)計和實現(xiàn)上的一大特色,也是面試過程中經(jīng)常遇到的話題。

三級緩存結(jié)構(gòu)

所謂的三級緩存,在 Spring 中表現(xiàn)為三個 Map 對象,定義在 DefaultSingletonBeanRegistry 類中,如下所示:

/** 單例對象的緩存: Bean名稱 --> Bean實例 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** 提前暴露的單例對象的緩存: Bean名稱 --> Bean實例(屬性未注入)*/
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** 單例對象工廠的緩存: Bean名稱 --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

請注意:

這里的 singletonObjects 就是第一級緩存,用來持有完整的 Bean 實例。

而 earlySingletonObjects 中存放的是那些提前暴露的對象,也就是已經(jīng)創(chuàng)建但還沒有完成屬性注入的對象,屬于第二級緩存。

最后的 singletonFactories 存放用來創(chuàng)建 earlySingletonObjects 的工廠對象,屬于第三級緩存。

三級緩存與保存的對象三級緩存與保存的對象

那么三級緩存是如何發(fā)揮作用的呢?讓我們來分析獲取 Bean 的代碼流程:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        //首先從一級緩存singletonObjects中獲取
        Object singletonObject = this.singletonObjects.get(beanName);
        //如果獲取不到,就從二級緩存earlySingletonObjects中獲取
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
           synchronized (this.singletonObjects) {
               singletonObject = this.earlySingletonObjects.get(beanName);
               //如果還是獲取不到,就從三級緩存singletonFactory中獲取
               if (singletonObject == null && allowEarlyReference) {
                   ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                   if (singletonFactory != null) {
                       singletonObject = singletonFactory.getObject();
                       //一旦獲取成功,就把對象從第三級緩存移動到第二級緩存中
                       .earlySingletonObjects.put(beanName, singletonObject);
                       this.singletonFactories.remove(beanName);
                   }
               }
           }
        }
        return singletonObject;
}

我們首先從一級緩存 singletonObjects 中獲取目前對象,如果獲取不到,則從二級緩存 earlySingletonObjects 中獲取;如果還是獲取不到,就從三級緩存 singletonFactory 中通過 ObjectFactory 進(jìn)行獲取。而一旦獲取成功,就會把目標(biāo)對象從第三級緩存移動到第二級緩存中,從而為下一次對象獲取過程做準(zhǔn)備。

通過這段代碼,我們了解了三級緩存的依次訪問過程,但可能你還是不理解 Spring 為什么要這樣設(shè)計。事實上,解決循環(huán)依賴的關(guān)鍵點還是在 Bean 的生命周期上。

在通過@Autowired 注解注入 Bean 時,真正完成 Bean 的創(chuàng)建是在一個 doCreateBean 方法中,該方法包括三個核心步驟:

通過 createBeanInstance 方法實例化 Bean。

通過 populateBean 方法實現(xiàn)屬性的注入。

最后通過 initializeBean 方法對 Bean 進(jìn)行擴展。

單例對象的初始化步驟示意圖單例對象的初始化步驟示意圖

對應(yīng)的代碼結(jié)構(gòu)如下所示:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
  //1. 初始化Bean實例
  instanceWrapper = createBeanInstance(beanName, mbd, args);
        //針對循環(huán)依賴問題暴露單例工廠類
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }
1
  //2. 對Bean實例執(zhí)行屬性注入
  populateBean(beanName, mbd, instanceWrapper);
  //3. 執(zhí)行初始化Bean實例的回調(diào)
  exposedObject = initializeBean(beanName, exposedObject, mbd);
  return exposedObject;
}

上面的第 1 步完成了 Bean 的初始化,而第 2 步才完成 Bean 的完整實例化。我們看到在第 1 步和第 2 步之間,存在一個 addSingletonFactory 方法,用于初始化這個第三級緩存中的數(shù)據(jù)。

Spring 解決循環(huán)依賴的訣竅就在于 singletonFactories 這個第三級緩存:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        Assert.notNull(singletonFactory, "Singleton factory must not be null");
        synchronized (this.singletonObjects) {
           if (!this.singletonObjects.containsKey(beanName)) {
               //添加Bean到第三級緩存中
               this.singletonFactories.put(beanName, singletonFactory);
               this.earlySingletonObjects.remove(beanName);
               this.registeredSingletons.add(beanName);
           }
        }
}

請注意,這段代碼的執(zhí)行時機是 Bean 已經(jīng)通過構(gòu)造函數(shù)進(jìn)行創(chuàng)建,但還沒有完成 Bean 中完整屬性的注入。換句話說,Bean 已經(jīng)可以被暴露出來進(jìn)行識別了,但還不能正常使用。接下來我們就來分析一下為什么通過這種機制就能解決循環(huán)依賴問題。

循環(huán)依賴解決方案

我們繼續(xù)討論 ClassA 和 ClassB 的循環(huán)依賴關(guān)系,基于 Setter 方法注入,整個流程如下所示,我們用紅色部分表示 ClassA 的創(chuàng)建過程,用黃色部分表示 ClassB 的創(chuàng)建過程。

基于 Setter 注入的循環(huán)依賴解決流程基于 Setter 注入的循環(huán)依賴解決流程

圖中,假設(shè)我們先初始化 ClassA。ClassA 首先通過 createBeanInstance 方法創(chuàng)建了實例,并且將這個實例提前暴露到第三級緩存 singletonFactories 中。然后,ClassA 嘗試通過 populateBean 方法注入屬性,發(fā)現(xiàn)自己依賴 ClassB 這個屬性,就會嘗試去獲取 ClassB 的實例。

顯然,這時候 ClassB 還沒有被創(chuàng)建,所以走創(chuàng)建流程。

ClassB 在初始化第一步的時候發(fā)現(xiàn)自己依賴了 ClassA,就會嘗試從第一級緩存 singletonObjects 去獲取 ClassA 的實例。因為 ClassA 這時候還沒有完全創(chuàng)建完畢,所以第一級緩存中不存在,同樣第二級緩存中也不存在。當(dāng)嘗試訪問第三級緩存時,因為 ClassA 已經(jīng)提前暴露了,所以 ClassB 能夠通過 singletonFactories 拿到 ClassA 對象并順利完成所有初始化流程。

ClassB 對象創(chuàng)建完成之后會把自己放到第一級緩存中,這時候 ClassA 就能從第一級緩存中獲取 ClassB 的實例,進(jìn)而完成 ClassA 的所有初始化流程。

講到這里,相信你能理解為什么構(gòu)造器注入無法解決循環(huán)依賴問題了。這是因為構(gòu)造器注入過程是發(fā)生在創(chuàng)建 Bean 的第一個步驟 createBeanInstance 中,而這個步驟中還沒有調(diào)用 addSingletonFactory 方法完成第三級緩存的構(gòu)建,自然也就無法從該緩存中獲取目標(biāo)對象。

總結(jié)

今天我們系統(tǒng)分析了 Spring 為開發(fā)人員提供的循環(huán)依賴解決方案。雖然基于 Spring 框架實現(xiàn) Bean 的依賴注入比較簡單,但也存在一些最佳實踐,尤其是在使用 Spring 的過程中,經(jīng)常碰到的循環(huán)依賴問題,需要開發(fā)人員對框架的底層原理有一定的了解。

于是基于 Spring 提供的三層緩存機制,我們對這一主題進(jìn)行了源碼級的深入分析。從源碼中,我們發(fā)現(xiàn) Spring 中解決循環(huán)依賴的核心思想在于:基于 Bean 的作用域和生命周期,把 Bean 的創(chuàng)建過程拆分成“實例化”和“屬性填充”這兩個階段,確保 Bean 對象能夠在第一個階段就能夠盡早暴露出來供其他 Bean 進(jìn)行使用。

而在 Spring 所提供的三種依賴注入類型中,也只有 Setter 方法注入能夠解決循環(huán)依賴問題,原因就在于這種注入類型生效的時機恰恰就在“實例化”階段之后、“屬性填充”階段之前。

責(zé)任編輯:武曉燕 來源: 程序員技術(shù)充電站
相關(guān)推薦

2024-06-05 11:43:10

2023-10-07 08:35:07

依賴注入Spring

2020-10-23 10:10:59

Promise前端代碼

2024-03-18 00:00:00

SpringBean設(shè)計

2023-09-28 08:15:05

SpringBean加載

2022-02-25 14:19:56

依賴管理前端命令

2021-04-07 20:01:23

Go變量常量

2020-11-27 06:28:55

Spring循環(huán)依賴

2025-04-21 04:00:00

2023-05-04 08:06:27

Spring循環(huán)依賴

2023-12-04 08:10:34

Spring循環(huán)依賴

2022-04-25 09:03:16

JavaScript代碼

2024-03-08 10:38:07

Vue響應(yīng)式數(shù)據(jù)

2022-03-26 08:49:13

MySQL數(shù)據(jù)存儲

2024-01-03 13:39:00

JS,Javascrip算法

2023-10-18 10:55:55

HashMap

2025-04-11 05:55:00

2025-01-13 16:00:00

服務(wù)網(wǎng)關(guān)分布式系統(tǒng)架構(gòu)

2024-06-21 08:32:24

2019-06-24 05:05:40

緩沖池查詢數(shù)據(jù)InnoDB
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 精品无码久久久久久国产 | 久久久久久免费精品一区二区三区 | 国产精品爱久久久久久久 | 中文字幕三区 | 精品欧美一区二区三区久久久 | 99久久精品国产一区二区三区 | 久久精品视频91 | 久久久久久久久久久久亚洲 | 亚洲精品无 | 无码日韩精品一区二区免费 | 欧美情趣视频 | 亚洲精品日韩在线 | 人人草天天草 | 久久伊人免费视频 | 黄色精品视频网站 | 国产激情在线 | 久久出精品 | 国产人成在线观看 | 久草网址| 黄色高清视频 | 一区二区三区亚洲 | 免费看91 | 国产视频福利 | 国产精品极品美女在线观看免费 | 久久躁日日躁aaaaxxxx | 在线中文字幕视频 | 成人在线电影在线观看 | 一区二区三区电影网 | 亚洲欧美国产毛片在线 | www.婷婷| 欧美久久一区二区三区 | 欧美一区二区三区在线观看 | 国产精品不卡视频 | 国产精品 欧美精品 | 久国产精品 | 欧美日韩亚洲国产 | 色综合天天综合网国产成人网 | 成人福利在线 | 一区二区伦理电影 | 国产欧美一区二区三区在线看 | 日韩av最新网址 |