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

Spring通過(guò)三級(jí)緩存解決循環(huán)依賴(lài)問(wèn)題的過(guò)程詳解

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

1. 三級(jí)緩存解決的問(wèn)題場(chǎng)景

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

在早期的 Spring 版本中是可以自動(dòng)解決的循環(huán)依賴(lài)的問(wèn)題的,

public class A {
	@Autowired
    private B b;
}

public class B {
	@Autowired
    private A a;
}

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

第一,要求互相依賴(lài)的 Bean 必須要是單例的 Bean。

這是因?yàn)閷?duì)于原型范圍的 Bean(prototype scope),每次請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的 Bean 實(shí)例,這樣每次嘗試解析循環(huán)依賴(lài)時(shí),都會(huì)產(chǎn)生新的 Bean 實(shí)例,導(dǎo)致無(wú)限循環(huán),由于沒(méi)有全局的、持續(xù)的單例實(shí)例的緩存來(lái)引用,因此循環(huán)依賴(lài)無(wú)法得到解決。

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

當(dāng)使用構(gòu)造函數(shù)注入時(shí),一個(gè) Bean 的實(shí)例在構(gòu)造函數(shù)被完全調(diào)用之前是不會(huì)被創(chuàng)建的;如果 Bean A 的構(gòu)造函數(shù)依賴(lài)于 Bean B,而 Bean B 的構(gòu)造函數(shù)又依賴(lài)于 Bean A,那么就會(huì)產(chǎn)生一個(gè)死鎖的情況,因?yàn)閮烧叨疾荒茉趯?duì)方初始化之前完成初始化。

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)于三級(jí)緩存的定義如下:

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

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

2. 三級(jí)緩存的差異性

一級(jí)緩存:

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

二級(jí)緩存:

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

三級(jí)緩存:

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

3. 循環(huán)依賴(lài)時(shí)的處理流程

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

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

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

常見(jiàn)疑問(wèn)解答

問(wèn)題一:為什么在 Bean B 被注入 Bean A 之前,需要將 Bean A 存入二級(jí)緩存?

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

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

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

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

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

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

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

  • 如果直接往二級(jí)緩存添加沒(méi)有被代理的 Bean,那么此時(shí)注入給其它對(duì)象的 Bean 可能跟最后完全生成的 Bean 是不一樣的,因?yàn)樽詈笊墒褂玫氖强赡艽韺?duì)象,此時(shí)注入的是原始對(duì)象,這這種情況是不允許發(fā)生的。
  • 如果直接往二級(jí)緩存添加一個(gè)代理 Bean,在不確定是否要使用代理對(duì)象的情況下,就有提前暴露代理對(duì)象的可能;正常的代理的對(duì)象都是初始化后期調(diào)用生成的,是基于后置處理器的,若提早的代理就違背了 Bean 定義的生命周期。

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

4. 源碼驗(yàn)證

在項(xiàng)目中雙擊 Shift,全局查找文件:AbstractAutowireCapableBeanFactory,找到 550 行左右的 doCreateBean 方法,重點(diǎn)看一下 580 行到 600 行這20行代碼就行,包含了三級(jí)緩存、屬性注入、初始化,精華都在這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);
    }
    // 通過(guò)BeanDefinition實(shí)例化對(duì)象
    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.
    // 對(duì)象是否單例、是否未創(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");
        }
        // 將對(duì)象的工廠(chǎng)加入到三級(jí)緩存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        // 屬性注入 (在這里解析@Autowired注解時(shí),觸發(fā)循環(huán)依賴(lài))
        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);
    // 從一級(jí)緩存中獲取
    // 如果一級(jí)緩存里沒(méi)有,且 Bean 正在創(chuàng)建中
    // 就從二級(jí)緩存里獲取
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        // 二級(jí)緩存沒(méi)有,就從三級(jí)緩存獲取一個(gè)工廠(chǎng)
        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) {
                            // 能獲取到工廠(chǎng)則創(chuàng)建 Bean
                            singletonObject = singletonFactory.getObject();
                            // 把實(shí)例存入二級(jí)緩存
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            // 把工廠(chǎng)從三級(jí)緩存移除
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

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

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

相關(guān)文章

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

    maven-compiler-plugin版本指定方式

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

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

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

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

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

    解決mybatis-plus3.1.1版本使用lambda表達(dá)式查詢(xún)報(bào)錯(cuò)的方法

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

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

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

    idea不使用maven如何將項(xiàng)目打包

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

    JDBC 實(shí)現(xiàn)通用的增刪改查基礎(chǔ)類(lèi)方法

    下面小編就為大家分享一篇JDBC 實(shí)現(xiàn)通用的增刪改查基礎(chǔ)類(lèi)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    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), 它可以進(jìn)行高效的插入和移除的操作,它基于的是一個(gè)雙向鏈表的結(jié)構(gòu),需要的朋友可以參考下
    2023-12-12
  • Jenkins自動(dòng)構(gòu)建部署項(xiàng)目到遠(yuǎn)程服務(wù)器上的方法步驟

    Jenkins自動(dòng)構(gòu)建部署項(xiàng)目到遠(yuǎn)程服務(wù)器上的方法步驟

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

    javaweb servlet生成簡(jiǎn)單驗(yàn)證碼

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

最新評(píng)論