最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring中循環(huán)依賴問題的解決機(jī)制及詳細(xì)流程

 更新時(shí)間:2025年08月26日 15:03:50   作者:無糖星軌  
本文給大家介紹Spring中循環(huán)依賴問題的解決機(jī)制總結(jié),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧

一、解決機(jī)制

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

循環(huán)依賴是指兩個(gè)或多個(gè)Bean之間相互依賴對方,形成一個(gè)閉環(huán)的依賴關(guān)系。最常見的情況是當(dāng)Bean A依賴Bean B,而Bean B又依賴Bean A時(shí),就形成了循環(huán)依賴。在Spring容器初始化過程中,如果不加以特殊處理,這種循環(huán)依賴會導(dǎo)致Bean的創(chuàng)建過程陷入死循環(huán),最終導(dǎo)致應(yīng)用啟動失敗。

例如以下代碼展示了一個(gè)典型的循環(huán)依賴場景:

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

2. Spring三級緩存機(jī)制概述

Spring框架通過巧妙的"三級緩存"機(jī)制解決了循環(huán)依賴問題。這三級緩存在Spring源碼中是通過三個(gè)Map集合實(shí)現(xiàn)的:

// 一級緩存:存放完全初始化好的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二級緩存:存放原始的Bean對象(尚未填充屬性),用于解決循環(huán)依賴
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三級緩存:存放Bean工廠對象,用于解決循環(huán)依賴
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

這三級緩存的作用分別是:

  • 一級緩存(singletonObjects):用于存儲完全初始化好的Bean,即已經(jīng)完成實(shí)例化、屬性填充和初始化的Bean,可以直接被其他對象使用。
  • 二級緩存(earlySingletonObjects):用于存儲早期曝光的Bean對象,這些Bean已經(jīng)完成實(shí)例化但還未完成屬性填充和初始化。當(dāng)出現(xiàn)循環(huán)依賴時(shí),其他Bean可以引用這個(gè)未完全初始化的Bean。
  • 三級緩存(singletonFactories):用于存儲Bean的工廠對象,主要用于處理需要AOP代理的Bean的循環(huán)依賴問題。它存儲的是一個(gè)Lambda表達(dá)式,這個(gè)表達(dá)式會返回Bean的早期引用,并在需要時(shí)將其放入二級緩存。

3. 三級緩存的查找順序

當(dāng)Spring需要獲取一個(gè)單例Bean時(shí),會按照以下順序查找:

  1. 首先從一級緩存(singletonObjects)中查找,如果找到直接返回
  2. 如果一級緩存沒有,再從二級緩存(earlySingletonObjects)中查找
  3. 如果二級緩存也沒有,則從三級緩存(singletonFactories)中查找對應(yīng)的工廠,如果找到則通過工廠獲取對象,并將其放入二級緩存,同時(shí)從三級緩存中移除

這種層級查找的機(jī)制確保了在循環(huán)依賴的情況下,Bean能夠被正確地創(chuàng)建和注入。

4. Spring解決循環(huán)依賴的詳細(xì)流程

以A、B兩個(gè)類相互依賴為例,Spring解決循環(huán)依賴的流程如下:

  • 創(chuàng)建Bean A
    • Spring首先創(chuàng)建Bean A的實(shí)例(僅完成實(shí)例化,未進(jìn)行屬性填充)
    • 將A的創(chuàng)建工廠放入三級緩存singletonFactories中
    • 開始給A填充屬性,發(fā)現(xiàn)依賴了B
  • 創(chuàng)建Bean B
    • Spring開始創(chuàng)建B(因?yàn)锳依賴B)
    • 實(shí)例化B,并將B的創(chuàng)建工廠放入三級緩存
    • 開始給B填充屬性,發(fā)現(xiàn)依賴了A
  • 處理循環(huán)依賴
    • 此時(shí)需要注入A,但A正在創(chuàng)建中
    • Spring嘗試從一級緩存查找A,未找到
    • 繼續(xù)從二級緩存查找A,未找到
    • 最后從三級緩存中找到A的工廠對象
    • 通過工廠獲取A的早期引用(可能是原始對象,也可能是代理對象)
    • 將A的早期引用放入二級緩存,并從三級緩存中移除A的工廠
    • 將A的早期引用注入到B中
  • 完成Bean創(chuàng)建
    • B完成屬性填充和初始化,放入一級緩存
    • 返回到A的屬性填充流程,將B注入到A中
    • A完成屬性填充和初始化,放入一級緩存

通過這個(gè)流程,Spring成功解決了循環(huán)依賴問題,關(guān)鍵在于提前暴露了Bean的早期引用。

5. 為什么需要三級緩存

很多人會疑惑,為什么需要三級緩存?二級緩存不能解決循環(huán)依賴問題嗎?

實(shí)際上,在不考慮AOP的情況下,二級緩存確實(shí)可以解決循環(huán)依賴問題。但Spring設(shè)計(jì)三級緩存的主要目的是為了處理AOP代理的情況。

在Spring中,AOP代理是在Bean生命周期的最后階段(初始化后)創(chuàng)建的。但如果出現(xiàn)循環(huán)依賴,就需要提前創(chuàng)建代理對象。三級緩存中的工廠對象可以在需要時(shí)(即真正出現(xiàn)循環(huán)依賴時(shí))才創(chuàng)建代理對象,而不是對所有Bean都提前創(chuàng)建代理。

這種設(shè)計(jì)有以下優(yōu)勢:

  1. 延遲代理對象的創(chuàng)建:只有在真正需要時(shí)才創(chuàng)建代理對象,避免了不必要的性能開銷
  2. 保持Bean生命周期的一致性:盡可能地保持Bean的標(biāo)準(zhǔn)生命周期流程
  3. 靈活處理各種代理場景:適應(yīng)不同的AOP實(shí)現(xiàn)和代理方式

6. 三級緩存的局限性

雖然三級緩存機(jī)制能夠解決大多數(shù)循環(huán)依賴問題,但它仍有一些局限性:

  • 不能解決構(gòu)造器注入的循環(huán)依賴:因?yàn)闃?gòu)造器注入發(fā)生在實(shí)例化階段,此時(shí)Bean還未被放入三級緩存,所以無法解決
  • 不能解決prototype作用域的循環(huán)依賴:三級緩存機(jī)制只對單例Bean有效,對于prototype作用域的Bean,Spring不會緩存其實(shí)例,因此無法解決其循環(huán)依賴
  • 不能解決多例Bean之間的循環(huán)依賴:原因同上,Spring不會緩存非單例Bean

7. 源碼分析

Spring解決循環(huán)依賴的核心源碼主要在DefaultSingletonBeanRegistry類中,關(guān)鍵方法包括:

  1. getSingleton(String beanName):按照一級、二級、三級緩存的順序查找Bean
  2. addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory):將Bean的工廠對象放入三級緩存
  3. doGetBean(String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly):獲取Bean的主要邏輯

AbstractAutowireCapableBeanFactorydoCreateBean方法中,會在Bean實(shí)例化后立即將其工廠對象放入三級緩存,為解決循環(huán)依賴做準(zhǔn)備。

8. 總結(jié)

Spring通過三級緩存機(jī)制巧妙地解決了循環(huán)依賴問題,其核心思想是將Bean的實(shí)例化和初始化分離,提前暴露實(shí)例化但未完全初始化的對象。三級緩存的設(shè)計(jì)不僅解決了基本的循環(huán)依賴問題,還優(yōu)雅地處理了AOP代理場景下的循環(huán)依賴。

這種機(jī)制體現(xiàn)了Spring框架設(shè)計(jì)的精妙之處,通過緩存分層和提前暴露對象的方式,在不影響B(tài)ean正常生命周期的前提下解決了看似棘手的循環(huán)依賴問題。

二、實(shí)際示例與解析

為了更直觀地理解Spring如何通過三級緩存解決循環(huán)依賴問題,我們來看一個(gè)具體的示例,并詳細(xì)分析整個(gè)過程。

1. 示例代碼

首先,我們創(chuàng)建兩個(gè)相互依賴的Service類:

@Service
public class UserService {
    @Autowired
    private OrderService orderService;
    public void findUserOrders(Long userId) {
        System.out.println("查詢用戶訂單");
        orderService.getOrdersByUserId(userId);
    }
}
@Service
public class OrderService {
    @Autowired
    private UserService userService;
    public List<Order> getOrdersByUserId(Long userId) {
        System.out.println("獲取用戶訂單");
        return new ArrayList<>();
    }
    public void notifyUser(Long orderId) {
        System.out.println("通知用戶訂單狀態(tài)");
        userService.findUserOrders(1L); // 調(diào)用UserService的方法
    }
}

在這個(gè)示例中,UserService依賴OrderService,而OrderService又依賴UserService,形成了典型的循環(huán)依賴。

2. 詳細(xì)解析Spring處理流程

讓我們詳細(xì)分析Spring如何處理這個(gè)循環(huán)依賴:

2.1 創(chuàng)建UserService

當(dāng)Spring容器啟動時(shí),會按照Bean定義順序開始創(chuàng)建Bean。假設(shè)先創(chuàng)建UserService

1. 實(shí)例化UserService對象(僅調(diào)用構(gòu)造函數(shù),此時(shí)內(nèi)部屬性尚未賦值)
2. 將UserService實(shí)例的創(chuàng)建工廠添加到三級緩存(singletonFactories)中
   singletonFactories.put("userService", () -> getEarlyBeanReference(beanName, mbd, userService實(shí)例))
3. 開始填充UserService的屬性,發(fā)現(xiàn)需要注入OrderService

2.2 創(chuàng)建OrderService

由于UserService依賴OrderService,Spring開始創(chuàng)建OrderService:

1. 實(shí)例化OrderService對象(僅調(diào)用構(gòu)造函數(shù))
2. 將OrderService實(shí)例的創(chuàng)建工廠添加到三級緩存中
   singletonFactories.put("orderService", () -> getEarlyBeanReference(beanName, mbd, orderService實(shí)例))
3. 開始填充OrderService的屬性,發(fā)現(xiàn)需要注入U(xiǎn)serService

2.3 處理循環(huán)依賴

此時(shí)出現(xiàn)了關(guān)鍵的循環(huán)依賴處理步驟:

1. OrderService需要注入U(xiǎn)serService,但UserService正在創(chuàng)建中
2. Spring嘗試從一級緩存(singletonObjects)查找UserService,未找到
3. 繼續(xù)從二級緩存(earlySingletonObjects)查找UserService,未找到
4. 最后從三級緩存(singletonFactories)中找到UserService的工廠對象
5. 調(diào)用工廠對象的getObject()方法獲取UserService的早期引用
   - 如果UserService需要被代理(如有@Transactional注解),此時(shí)會創(chuàng)建代理對象
   - 如果不需要代理,則返回原始對象
6. 將獲取到的UserService早期引用放入二級緩存,同時(shí)從三級緩存中移除
   earlySingletonObjects.put("userService", userService早期引用)
   singletonFactories.remove("userService")
7. 將UserService的早期引用注入到OrderService中

2.4 完成Bean創(chuàng)建

接下來完成兩個(gè)Bean的創(chuàng)建過程:

1. OrderService完成屬性填充(已注入U(xiǎn)serService的早期引用)
2. OrderService完成初始化(調(diào)用各種初始化方法)
3. 如果OrderService需要被代理,創(chuàng)建代理對象(AOP)
4. 將完全初始化好的OrderService放入一級緩存
   singletonObjects.put("orderService", 完全初始化的orderService)
5. 返回到UserService的屬性填充流程,將完全初始化好的OrderService注入到UserService中
6. UserService完成初始化
7. 如果UserService需要被代理且之前沒有提前創(chuàng)建代理,創(chuàng)建代理對象
8. 將完全初始化好的UserService放入一級緩存
   singletonObjects.put("userService", 完全初始化的userService)

3. 關(guān)鍵源碼執(zhí)行分析

讓我們看一下在這個(gè)過程中涉及的關(guān)鍵源碼執(zhí)行流程:

3.1 從getSingleton方法開始

當(dāng)Spring嘗試獲取一個(gè)Bean時(shí),首先會調(diào)用getSingleton方法:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 首先從一級緩存查找
    Object singletonObject = this.singletonObjects.get(beanName);
    // 如果一級緩存沒有,且該Bean正在創(chuàng)建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 從二級緩存查找
        singletonObject = this.earlySingletonObjects.get(beanName);
        // 如果二級緩存也沒有,且允許早期引用
        if (singletonObject == null && allowEarlyReference) {
            // 從三級緩存獲取工廠
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
                // 通過工廠獲取早期引用
                singletonObject = singletonFactory.getObject();
                // 放入二級緩存
                this.earlySingletonObjects.put(beanName, singletonObject);
                // 從三級緩存移除
                this.singletonFactories.remove(beanName);
            }
        }
    }
    return singletonObject;
}

3.2 添加Bean到三級緩存

在Bean實(shí)例化后,Spring會將其添加到三級緩存中:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        // 如果一級緩存中不存在
        if (!this.singletonObjects.containsKey(beanName)) {
            // 添加到三級緩存
            this.singletonFactories.put(beanName, singletonFactory);
            // 確保二級緩存中不存在
            this.earlySingletonObjects.remove(beanName);
            // 記錄注冊的單例
            this.registeredSingletons.add(beanName);
        }
    }
}

3.3 創(chuàng)建早期引用

當(dāng)需要處理循環(huán)依賴時(shí),Spring會通過getEarlyBeanReference方法獲取早期引用:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = 
                    (SmartInstantiationAwareBeanPostProcessor) bp;
                // 這里可能創(chuàng)建代理對象
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

4. AOP場景下的循環(huán)依賴

特別值得注意的是,當(dāng)涉及到AOP代理時(shí),循環(huán)依賴的處理會更加復(fù)雜。例如,如果我們的示例中添加了事務(wù)注解:

@Service
@Transactional
public class UserService {
    // ...
}
@Service
@Transactional
public class OrderService {
    // ...
}

在這種情況下:

  1. 當(dāng)從三級緩存獲取UserService的早期引用時(shí),會通過AbstractAutoProxyCreator.getEarlyBeanReference方法創(chuàng)建代理對象
  2. 這個(gè)代理對象會被放入二級緩存,并最終注入到OrderService中
  3. 這就確保了OrderService依賴的是UserService的代理對象,而不是原始對象

這也解釋了為什么需要三級緩存而不是二級緩存:三級緩存中存儲的工廠可以在需要時(shí)(出現(xiàn)循環(huán)依賴時(shí))才創(chuàng)建代理對象,而不是對所有Bean都提前創(chuàng)建代理。

5. 總結(jié)

通過這個(gè)實(shí)際示例,我們可以清晰地看到Spring如何通過三級緩存機(jī)制解決循環(huán)依賴問題:

  1. 實(shí)例化Bean后立即將其工廠對象放入三級緩存
  2. 當(dāng)出現(xiàn)循環(huán)依賴時(shí),通過三級緩存獲取早期引用(可能是代理對象)
  3. 將早期引用放入二級緩存,并從三級緩存中移除
  4. 使用早期引用完成依賴注入
  5. 最終將完全初始化的Bean放入一級緩存

這種機(jī)制既解決了循環(huán)依賴問題,又保持了Spring Bean生命周期的完整性,同時(shí)還能靈活處理AOP代理場景,體現(xiàn)了Spring框架設(shè)計(jì)的精妙之處。

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

相關(guān)文章

  • Java版本怎么選以及JDK各版本特性對比與實(shí)戰(zhàn)建議

    Java版本怎么選以及JDK各版本特性對比與實(shí)戰(zhàn)建議

    Java是一種廣泛使用的編程語言,擁有一個(gè)龐大的社區(qū)和大量的生態(tài)系統(tǒng),自從Java的早期版本以來,它已經(jīng)經(jīng)歷了許多變化和改進(jìn),這篇文章主要介紹了Java版本怎么選以及JDK各版本特性對比與實(shí)戰(zhàn)建議的相關(guān)資料,需要的朋友可以參考下
    2025-11-11
  • java求整數(shù)的位數(shù)方式

    java求整數(shù)的位數(shù)方式

    這篇文章主要介紹了java求整數(shù)的位數(shù)方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Java8優(yōu)雅的字符串拼接工具類StringJoiner實(shí)例代碼

    Java8優(yōu)雅的字符串拼接工具類StringJoiner實(shí)例代碼

    這篇文章主要給大家介紹了關(guān)于Java8優(yōu)雅的字符串拼接工具類StringJoiner的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-02-02
  • Lombok中@EqualsAndHashCode注解的使用及說明

    Lombok中@EqualsAndHashCode注解的使用及說明

    這篇文章主要介紹了Lombok中@EqualsAndHashCode注解的使用及說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • LeetCode程序員面試題之遞歸乘法

    LeetCode程序員面試題之遞歸乘法

    在Java中,遞歸乘法是一種簡單而有效的方法,可以用來計(jì)算兩個(gè)數(shù)字的乘積。它的基本思想是:如果第一個(gè)數(shù)字是0,則乘積為0;如果第一個(gè)數(shù)字是1,則乘積為第二個(gè)數(shù)字;其他情況,則通過將第一個(gè)數(shù)字減1,并將第二個(gè)數(shù)字與自身相乘,來實(shí)現(xiàn)遞歸乘法。
    2023-02-02
  • java實(shí)現(xiàn)的根據(jù)概率隨機(jī)中獎測試類

    java實(shí)現(xiàn)的根據(jù)概率隨機(jī)中獎測試類

    這篇文章主要介紹了java實(shí)現(xiàn)的根據(jù)概率隨機(jī)中獎測試類,結(jié)合完整實(shí)例形式詳細(xì)分析了java隨機(jī)數(shù)實(shí)現(xiàn)概率運(yùn)算相關(guān)操作技巧,需要的朋友可以參考下
    2019-09-09
  • Spring Data Redis對象緩存序列化問題解決

    Spring Data Redis對象緩存序列化問題解決

    相信在項(xiàng)目中,你一定是經(jīng)常使用Redis,在使用時(shí),有沒有遇到同我一樣,對象緩存序列化問題的呢,本文主要介紹了Spring Data Redis對象緩存序列化問題解決,感興趣的可以了解一下
    2024-01-01
  • springboot+dubbo實(shí)現(xiàn)時(shí)間輪算法

    springboot+dubbo實(shí)現(xiàn)時(shí)間輪算法

    時(shí)間輪是一種高效利用線程資源進(jìn)行批量化調(diào)度的算法,本文主要介紹了springboot+dubbo實(shí)現(xiàn)時(shí)間輪算法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-04-04
  • SpringBoot整合Mybatis與druid實(shí)現(xiàn)流程詳解

    SpringBoot整合Mybatis與druid實(shí)現(xiàn)流程詳解

    這篇文章主要介紹了springboot整合mybatis plus與druid詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的下伙伴可以參考一下
    2022-10-10
  • SpringBoot整合RabbitMQ處理死信隊(duì)列和延遲隊(duì)列

    SpringBoot整合RabbitMQ處理死信隊(duì)列和延遲隊(duì)列

    這篇文章將通過示例為大家詳細(xì)介紹SpringBoot整合RabbitMQ時(shí)如何處理死信隊(duì)列和延遲隊(duì)列,文中的示例代碼講解詳細(xì),需要的可以參考一下
    2022-05-05

最新評論

蒙自县| 临安市| 砚山县| 建阳市| 渝北区| 麟游县| 绥江县| 涞水县| 安平县| 贵南县| 甘谷县| 革吉县| 武胜县| 深圳市| 庆安县| 彭水| 潼南县| 威远县| 张家港市| 兰考县| 深水埗区| 达日县| 岑溪市| 锡林浩特市| 义马市| 卓资县| 清远市| 遵化市| 武穴市| 祁连县| 雷州市| 舟曲县| 南漳县| 锡林郭勒盟| 麟游县| 辽中县| 枣庄市| 尼勒克县| 浦北县| 德钦县| 乌鲁木齐县|