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

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

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

一、解決機(jī)制

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

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

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

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

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

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

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

這三級(jí)緩存的作用分別是:

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

3. 三級(jí)緩存的查找順序

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

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

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

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

5. 為什么需要三級(jí)緩存

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

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

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

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

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

6. 三級(jí)緩存的局限性

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

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

7. 源碼分析

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

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

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

8. 總結(jié)

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

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

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

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

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處理流程

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

2.1 創(chuàng)建UserService

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

1. 實(shí)例化UserService對(duì)象(僅調(diào)用構(gòu)造函數(shù),此時(shí)內(nèi)部屬性尚未賦值)
2. 將UserService實(shí)例的創(chuàng)建工廠添加到三級(jí)緩存(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對(duì)象(僅調(diào)用構(gòu)造函數(shù))
2. 將OrderService實(shí)例的創(chuàng)建工廠添加到三級(jí)緩存中
   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嘗試從一級(jí)緩存(singletonObjects)查找UserService,未找到
3. 繼續(xù)從二級(jí)緩存(earlySingletonObjects)查找UserService,未找到
4. 最后從三級(jí)緩存(singletonFactories)中找到UserService的工廠對(duì)象
5. 調(diào)用工廠對(duì)象的getObject()方法獲取UserService的早期引用
   - 如果UserService需要被代理(如有@Transactional注解),此時(shí)會(huì)創(chuàng)建代理對(duì)象
   - 如果不需要代理,則返回原始對(duì)象
6. 將獲取到的UserService早期引用放入二級(jí)緩存,同時(shí)從三級(jí)緩存中移除
   earlySingletonObjects.put("userService", userService早期引用)
   singletonFactories.remove("userService")
7. 將UserService的早期引用注入到OrderService中

2.4 完成Bean創(chuàng)建

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

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

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

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

3.1 從getSingleton方法開始

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

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

3.2 添加Bean到三級(jí)緩存

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

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

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

當(dāng)需要處理循環(huán)依賴時(shí),Spring會(huì)通過(guò)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)建代理對(duì)象
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

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

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

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

在這種情況下:

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

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

5. 總結(jié)

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

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

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

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

相關(guān)文章

  • Hibernate緩存詳解

    Hibernate緩存詳解

    本文主要介紹了Hibernate緩存的相關(guān)知識(shí)。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-02-02
  • K8S(Docker)如何優(yōu)雅的關(guān)閉SpringBoot微服務(wù)

    K8S(Docker)如何優(yōu)雅的關(guān)閉SpringBoot微服務(wù)

    這篇文章主要介紹了K8S(Docker)如何優(yōu)雅的關(guān)閉SpringBoot微服務(wù)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-01-01
  • Java面試題之HashSet的實(shí)現(xiàn)原理

    Java面試題之HashSet的實(shí)現(xiàn)原理

    這篇文章主要介紹了Java面試題之HashSet的實(shí)現(xiàn)原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • Java項(xiàng)目自動(dòng)生成接口文檔教程

    Java項(xiàng)目自動(dòng)生成接口文檔教程

    本文主要介紹了Java項(xiàng)目自動(dòng)生成接口文檔教程,包含使用Apifox插件從IDEA生成的文檔,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-03-03
  • Spring?Batch批處理框架操作指南

    Spring?Batch批處理框架操作指南

    Spring?Batch?是?Spring?提供的一個(gè)數(shù)據(jù)處理框架。企業(yè)域中的許多應(yīng)用程序需要批量處理才能在關(guān)鍵任務(wù)環(huán)境中執(zhí)行業(yè)務(wù)操作,這篇文章主要介紹了Spring?Batch批處理框架操作指南,需要的朋友可以參考下
    2022-07-07
  • Java中的OpenJDK使用原理

    Java中的OpenJDK使用原理

    這篇文章主要介紹了Java中的OpenJDK使用原理,OpenJDK是Java的開發(fā)工具包,關(guān)于Java為什么要使用它文章作簡(jiǎn)單介紹,感興趣的朋友可以參考一下
    2022-06-06
  • Java使用Tesseract-Ocr識(shí)別數(shù)字

    Java使用Tesseract-Ocr識(shí)別數(shù)字

    這篇文章主要介紹了Java使用Tesseract-Ocr識(shí)別數(shù)字的方法,幫助大家更好的理解和學(xué)習(xí)使用Java,感興趣的朋友可以了解下
    2021-04-04
  • SpringBoot項(xiàng)目中獲取resources下靜態(tài)文件時(shí)遇到的坑及解決

    SpringBoot項(xiàng)目中獲取resources下靜態(tài)文件時(shí)遇到的坑及解決

    文章總結(jié):在項(xiàng)目中使用靜態(tài)圖片時(shí),遇到j(luò)ar包部署到linux服務(wù)器報(bào)錯(cuò)的問(wèn)題,解決方法包括將圖片上傳到服務(wù)器指定文件夾或使用ClassPathResource讀取,前者需要維護(hù),后者更方便
    2024-11-11
  • Java Lombok簡(jiǎn)介、使用、工作原理、優(yōu)缺點(diǎn)

    Java Lombok簡(jiǎn)介、使用、工作原理、優(yōu)缺點(diǎn)

    這篇文章主要介紹了Java Lombok簡(jiǎn)介、使用、工作原理、優(yōu)缺點(diǎn)的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用Java Lombok,感興趣的朋友可以了解下
    2021-03-03
  • Spring Web MVC框架學(xué)習(xí)之配置Spring Web MVC

    Spring Web MVC框架學(xué)習(xí)之配置Spring Web MVC

    這一篇文章講的是Spring Web MVC各部分的配置方法,包括Java代碼配置和XML文件配置以及MVC命名空間的使用方法。
    2017-03-03

最新評(píng)論