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

Spring通過三級緩存解決循環(huán)依賴問題的過程詳解

 更新時間:2023年10月27日 09:03:39   作者:韻秋梧桐  
循環(huán)依賴指的是在對象之間存在相互依賴關(guān)系,形成一個閉環(huán),導致無法準確地完成對象的創(chuàng)建和初始化,本文主要介紹了Spring通過三級緩存解決循環(huán)依賴的方法,需要的可以參考下

1. 三級緩存解決的問題場景

循環(huán)依賴指的是在對象之間存在相互依賴關(guān)系,形成一個閉環(huán),導致無法準確地完成對象的創(chuàng)建和初始化;當兩個或多個對象彼此之間相互引用,而這種相互引用形成一個循環(huán)時,就可能出現(xiàn)循環(huán)依賴問題。

在早期的 Spring 版本中是可以自動解決的循環(huán)依賴的問題的,

public class A {
	@Autowired
    private B b;
}

public class B {
	@Autowired
    private A a;
}

但要注意,Spring 解決循環(huán)依賴是有前提條件的,

第一,要求互相依賴的 Bean 必須要是單例的 Bean。

這是因為對于原型范圍的 Bean(prototype scope),每次請求都會創(chuàng)建一個新的 Bean 實例,這樣每次嘗試解析循環(huán)依賴時,都會產(chǎn)生新的 Bean 實例,導致無限循環(huán),由于沒有全局的、持續(xù)的單例實例的緩存來引用,因此循環(huán)依賴無法得到解決。

第二,依賴注入的方式不能都是構(gòu)造函數(shù)注入的方式。

當使用構(gòu)造函數(shù)注入時,一個 Bean 的實例在構(gòu)造函數(shù)被完全調(diào)用之前是不會被創(chuàng)建的;如果 Bean A 的構(gòu)造函數(shù)依賴于 Bean B,而 Bean B 的構(gòu)造函數(shù)又依賴于 Bean A,那么就會產(chǎn)生一個死鎖的情況,因為兩者都不能在對方初始化之前完成初始化。

public class C {
    private D d;
    @Autowired
    public C(D d) {
        this.dService = dService;
    }
}

public class D {
    private C c;
    @Autowired
    public D(C c) {
        this.c = c;
    }
}

Spring 源碼中關(guān)于三級緩存的定義如下:

// 一級緩存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二級緩存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三級緩存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

所以說,所謂的“三級緩存”就是是指三個 Map 數(shù)據(jù)結(jié)構(gòu),分別用于存儲不同狀態(tài)的 Bean。

2. 三級緩存的差異性

一級緩存:

一級緩存保存的是已經(jīng)完全初始化和實例化的 Bean 對象,在程序中使用的 Bean 通常就是從這個緩存中獲取的;這個緩存的目的是確保 Bean 只初始化一次(是單例的),避免多次實例化相同的Bean對象,提高性能。

二級緩存:

二級緩存用來解決 Bean 創(chuàng)建過程中的循環(huán)依賴問題,它存儲的是尚未完成屬性注入和初始化的“半成品”Bean 對象;當 Spring容器發(fā)現(xiàn)兩個或多個 Bean 之間存在循環(huán)依賴時,也就是當一個 Bean 創(chuàng)建過程中需要引用另一個正在創(chuàng)建的 Bean,Spring 將創(chuàng)建需要的這些未完全初始化的對象提前暴露在二級緩存中,以便其他 Bean 進行引用,確保它們之間的依賴能夠被滿足。

三級緩存:

三級緩存中存儲的是 ObjectFactory<?> 類型的代理工廠對象,主要用于處理存在 AOP 時的循環(huán)依賴問題;每個 Bean 都對應一個 ObjectFactory 對象,通過調(diào)用該對象的 getObject 方法,可以獲取到早期暴露出去的 Bean;在該 Bean 要被其他 Bean 引用時,Spring 就會用工廠對象創(chuàng)建出該 Bean 的實例對象,最終當該 Bean 完成構(gòu)造的所有步驟后就會將該 Bean 放入到一級緩存中。

3. 循環(huán)依賴時的處理流程

當 Spring 發(fā)生循環(huán)依賴時(以最開始介紹的場景為例,A B 兩個 Bean 相互依賴),以下是完善的執(zhí)行流程:

  • 遍歷待創(chuàng)建的所有 beanName:在容器啟動時,Spring 會遍歷所有需要創(chuàng)建的 Bean 名稱,在第一次遍歷到 A 時,就開始獲取 Bean A;如果 Bean A 的實例不在一級、二級緩存中(緩存中沒有值),Spring 會開始正常的 Bean 創(chuàng)建流程。
  • Bean A 的創(chuàng)建:Bean A 的創(chuàng)建過程開始,然后 Spring 會檢查是否 A 是單例(Singleton),同時 A 是否已經(jīng)創(chuàng)建完成;如果 A 是單例且尚未創(chuàng)建完成,將 A 的 BeanFactory 存入三級緩存。
  • 處理依賴注入:A 開始處理 @Autowired 注解,嘗試注入 B 屬性;Spring 會在一級、二級緩存中來查找 Bean B,如果找不到,則開始正常創(chuàng)建 Bean B 的流程。
  • Bean B 的創(chuàng)建:Bean B 的創(chuàng)建過程類似于 A,首先判斷B是否是單例,且是否已創(chuàng)建完成,如果否,B 的BeanFactory 也會被存入三級緩存。
  • B 注入 A 屬性:B 開始注入 A 屬性,嘗試從一級、二級緩存中查找 A;如果在緩存中找不到 A,B 會嘗試從三級緩存獲取 A 的 BeanFactory,并通過 BeanFactory的getObject()方法獲取 A 的屬性,此時,A 被存入二級緩存,同時清除三級緩存;因此,B 能夠成功注入 A 屬性,B 接著執(zhí)行初始化,處于實例化和初始化都已完成的完全狀態(tài)。
  • B 存入一級緩存:B執(zhí)行addSingleton(),將完全狀態(tài)的 B 存入一級緩存,并清空二級,三級緩存。
  • A 繼續(xù)注入 B 屬性:A 繼續(xù)注入 B 屬性,調(diào)用beanFactory.getBean()方法獲取 B,由于第六步已經(jīng)將 B 存入一級緩存,A 可以直接獲取 B,成功注入 B 屬性, A 接著執(zhí)行初始化,得到一個完全狀態(tài)的 A。
  • A 存入一級緩存:A 執(zhí)行addSingleton(),將完全狀態(tài)的 A 存入一級緩存,并清空二級緩存。

此時,A 和 B 都被實例化和初始化完成,解決了循環(huán)依賴的問題;這個流程確保了每個Bean在需要時都能夠獲取到已完全初始化的依賴項。

常見疑問解答

問題一:為什么在 Bean B 被注入 Bean A 之前,需要將 Bean A 存入二級緩存?

主要原因是,如果存在其他循環(huán)依賴需要用到 A,從二級緩存中直接取出早期的 Bean A 對象會更加高效。

問題二:為什么創(chuàng)建完 Bean 后要清空二、三級緩存?

清空是為了節(jié)省存儲空間,一旦 Bean 完全初始化并存儲在一級緩存中,其在二、三級緩存中的記錄就不再需要了。

問題三:三級緩存為什么不能解決構(gòu)造器引起的循環(huán)依賴?

這是因為構(gòu)造器引起的循環(huán)依賴發(fā)生在 Bean 的實例化階段,這個階段比二、三級緩存處理的階段還要早,無法創(chuàng)建出早期的半成品對象。

問題四:如果不使用三級緩存,只使用二級緩存,能否解決循環(huán)依賴?

肯定是不能的,二級緩存存儲的 Bean 可能是兩種類型,一種是實例化階段創(chuàng)建出來的對象,另一種是實例化階段創(chuàng)建出來的對象的代理對象;是否需要代理對象取決于你的配置需要,如是否添加了事務注解或自定義 AOP 切面;如果放棄使用三級緩存,即沒有 ObjectFactory,那么就需要將早期的 Bean 放入二級緩存;但問題是,應該將未被代理的 Bean 還是代理的 Bean 放入二級緩存,這只能在屬性注入階段,處理注解時才能分辨。

  • 如果直接往二級緩存添加沒有被代理的 Bean,那么此時注入給其它對象的 Bean 可能跟最后完全生成的 Bean 是不一樣的,因為最后生成使用的是可能代理對象,此時注入的是原始對象,這這種情況是不允許發(fā)生的。
  • 如果直接往二級緩存添加一個代理 Bean,在不確定是否要使用代理對象的情況下,就有提前暴露代理對象的可能;正常的代理的對象都是初始化后期調(diào)用生成的,是基于后置處理器的,若提早的代理就違背了 Bean 定義的生命周期。

Spring 在一個三級緩存放置一個工廠,如果產(chǎn)生循環(huán)依賴 ,這個工廠的作用就是判斷這個對象是否需要代理,如果否則直接返回,如果是則返回代理對像。

4. 源碼驗證

在項目中雙擊 Shift,全局查找文件:AbstractAutowireCapableBeanFactory,找到 550 行左右的 doCreateBean 方法,重點看一下 580 行到 600 行這20行代碼就行,包含了三級緩存、屬性注入、初始化,精華都在這20行,下面在源碼中給出了關(guān)鍵注釋。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
        throws BeanCreationException {

    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    // 通過BeanDefinition實例化對象
    if (instanceWrapper == null) {
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
        mbd.resolvedTargetType = beanType;
    }

    // Allow post-processors to modify the merged bean definition.
    synchronized (mbd.postProcessingLock) {
        if (!mbd.postProcessed) {
            try {
                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            }
            catch (Throwable ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                        "Post-processing of merged bean definition failed", ex);
            }
            mbd.postProcessed = true;
        }
    }

    // Eagerly cache singletons to be able to resolve circular references
    // even when triggered by lifecycle interfaces like BeanFactoryAware.
    // 對象是否單例、是否未創(chuàng)建完成
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isTraceEnabled()) {
            logger.trace("Eagerly caching bean '" + beanName +
                    "' to allow for resolving potential circular references");
        }
        // 將對象的工廠加入到三級緩存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        // 屬性注入 (在這里解析@Autowired注解時,觸發(fā)循環(huán)依賴)
        populateBean(beanName, mbd, instanceWrapper);
        // 初始化
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
        if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
            throw (BeanCreationException) ex;
        }
        else {
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
        }
    }

    if (earlySingletonExposure) {
        // 從緩存中獲取 Bean
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                if (!actualDependentBeans.isEmpty()) {
                    throw new BeanCurrentlyInCreationException(beanName,
                            "Bean with name '" + beanName + "' has been injected into other beans [" +
                            StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                            "] in its raw version as part of a circular reference, but has eventually been " +
                            "wrapped. This means that said other beans do not use the final version of the " +
                            "bean. This is often the result of over-eager type matching - consider using " +
                            "'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
                }
            }
        }
    }

    // Register bean as disposable.
    try {
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }

    return exposedObject;
}

從緩存中獲取 Bean 的源碼

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Quick check for existing instance without full singleton lock
    Object singletonObject = this.singletonObjects.get(beanName);
    // 從一級緩存中獲取
    // 如果一級緩存里沒有,且 Bean 正在創(chuàng)建中
    // 就從二級緩存里獲取
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        // 二級緩存沒有,就從三級緩存獲取一個工廠
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // Consistent creation of early reference within full sin
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            // 能獲取到工廠則創(chuàng)建 Bean
                            singletonObject = singletonFactory.getObject();
                            // 把實例存入二級緩存
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            // 把工廠從三級緩存移除
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

總之,Spring 的三級緩存機制是一個巧妙的設計,它解決了在 Bean 初始化過程中可能出現(xiàn)的循環(huán)依賴問題;對于 Spring 的用戶來說,這意味著更加穩(wěn)定和可靠的 Bean 管理和依賴注入機制。

以上就是Spring通過三級緩存解決循環(huán)依賴問題的過程詳解的詳細內(nèi)容,更多關(guān)于Spring三級緩存解決循環(huán)依賴的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • maven-compiler-plugin版本指定方式

    maven-compiler-plugin版本指定方式

    這篇文章主要介紹了maven-compiler-plugin版本指定方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • java 流與 byte[] 的互轉(zhuǎn)操作

    java 流與 byte[] 的互轉(zhuǎn)操作

    這篇文章主要介紹了java 流與 byte[] 的互轉(zhuǎn)操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10
  • Java gRPC攔截器簡單實現(xiàn)分布式日志鏈路追蹤器過程詳解

    Java gRPC攔截器簡單實現(xiàn)分布式日志鏈路追蹤器過程詳解

    有請求的發(fā)送、處理,當然就會有攔截器的需求,例如在服務端通過攔截器統(tǒng)一進行請求認證等操作,這些就需要攔截器來完成,今天松哥先和小伙伴們來聊一聊gRPC中攔截器的基本用法,后面我再整一篇文章和小伙伴們做一個基于攔截器實現(xiàn)的JWT認證的gRPC
    2023-03-03
  • 解決mybatis-plus3.1.1版本使用lambda表達式查詢報錯的方法

    解決mybatis-plus3.1.1版本使用lambda表達式查詢報錯的方法

    這篇文章主要介紹了解決mybatis-plus3.1.1版本使用lambda表達式查詢報錯的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-08-08
  • 詳解IDEA 中使用Maven創(chuàng)建項目常見錯誤和使用技巧(推薦)

    詳解IDEA 中使用Maven創(chuàng)建項目常見錯誤和使用技巧(推薦)

    這篇文章主要介紹了詳解IDEA 中使用Maven創(chuàng)建項目常見錯誤和使用技巧(推薦),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • idea不使用maven如何將項目打包

    idea不使用maven如何將項目打包

    使用IDEA 2021版本,不借助Maven進行打WAR包的步驟是:首先點擊Project Structure,然后點擊Artifacts,接著選擇需要的打包類型,設置好包的名稱,最后進行打包,這種方法適用于不使用Maven進行項目管理的情況
    2024-09-09
  • JDBC 實現(xiàn)通用的增刪改查基礎類方法

    JDBC 實現(xiàn)通用的增刪改查基礎類方法

    下面小編就為大家分享一篇JDBC 實現(xiàn)通用的增刪改查基礎類方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • Java集合之LinkedList源碼解析

    Java集合之LinkedList源碼解析

    這篇文章主要介紹了Java集合之LinkedList源碼解析,LinkedList和ArrayList數(shù)據(jù)結(jié)構(gòu)是完全不一樣的,ArrayList 底層是數(shù)組的結(jié)構(gòu),而 LinkedList 的底層則是鏈表的結(jié)構(gòu), 它可以進行高效的插入和移除的操作,它基于的是一個雙向鏈表的結(jié)構(gòu),需要的朋友可以參考下
    2023-12-12
  • Jenkins自動構(gòu)建部署項目到遠程服務器上的方法步驟

    Jenkins自動構(gòu)建部署項目到遠程服務器上的方法步驟

    這篇文章主要介紹了Jenkins自動構(gòu)建部署項目到遠程服務器上的方法步驟,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-01-01
  • javaweb servlet生成簡單驗證碼

    javaweb servlet生成簡單驗證碼

    這篇文章主要為大家詳細介紹了javaweb servlet生成簡單驗證碼,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-03-03

最新評論