溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Spring中怎么解決循環(huán)依賴

發(fā)布時間:2021-06-18 18:04:00 來源:億速云 閱讀:454 作者:Leah 欄目:大數(shù)據(jù)

今天就跟大家聊聊有關(guān)Spring中怎么解決循環(huán)依賴,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。


一、循環(huán)依賴是什么?

循環(huán)依賴,其實就是循環(huán)引用,就是兩個或者兩個以上的 bean 互相引用對方,最終形成一個閉環(huán),如 A 依賴 B,B 依賴 C,C 依賴 A。如下圖所示:

Spring中怎么解決循環(huán)依賴

Spring中的循環(huán)依賴,其實就是一個死循環(huán)的過程,在初始化 A 的時候發(fā)現(xiàn)依賴了 B,這時就會去初始化 B,然后又發(fā)現(xiàn) B 依賴 C,跑去初始化 C,初始化 C 的時候發(fā)現(xiàn)依賴了 A,則又會去初始化 A,依次循環(huán)永不退出,除非有終結(jié)條件。

一般來說,Spring 循環(huán)依賴的情況有兩種:

構(gòu)造器的循環(huán)依賴。 field 屬性的循環(huán)依賴。 對于構(gòu)造器的循環(huán)依賴,Spring 是無法解決的,只能拋出 BeanCurrentlyInCreationException 異常表示循環(huán)依賴,所以下面我們分析的都是基于 field 屬性的循環(huán)依賴。

在前文 Spring Ioc源碼分析 之 Bean的加載(三):各個 scope 的 Bean 創(chuàng)建 中提到,Spring 只解決 scope 為 singleton 的循環(huán)依賴。對于scope 為 prototype 的 bean ,Spring 無法解決,直接拋出 BeanCurrentlyInCreationException 異常。

為什么 Spring 不處理 prototype bean 呢?其實如果理解 Spring 是如何解決 singleton bean 的循環(huán)依賴就明白了。這里先留個疑問,我們先來看下 Spring 是如何解決 singleton bean 的循環(huán)依賴的。

二、解決singleton循環(huán)依賴

在AbstractBeanFactory 的 doGetBean()方法中,我們根據(jù)BeanName去獲取Singleton Bean的時候,會先從緩存獲取。
代碼如下:

//DefaultSingletonBeanRegistry.java

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 從一級緩存緩存 singletonObjects 中加載 bean
    Object singletonObject = this.singletonObjects.get(beanName);
    // 緩存中的 bean 為空,且當(dāng)前 bean 正在創(chuàng)建
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 加鎖
        synchronized (this.singletonObjects) {
            // 從 二級緩存 earlySingletonObjects 中獲取
            singletonObject = this.earlySingletonObjects.get(beanName);
            // earlySingletonObjects 中沒有,且允許提前創(chuàng)建
            if (singletonObject == null && allowEarlyReference) {
                // 從 三級緩存 singletonFactories 中獲取對應(yīng)的 ObjectFactory
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    //從單例工廠中獲取bean
                    singletonObject = singletonFactory.getObject();
                    // 添加到二級緩存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 從三級緩存中刪除
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

這段代碼涉及的3個關(guān)鍵的變量,分別是3個級別的緩存,定義如下:

	/** Cache of singleton objects: bean name --> bean instance */
	//單例bean的緩存 一級緩存
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name --> ObjectFactory */
	//單例對象工廠緩存 三級緩存
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name --> bean instance */
	//預(yù)加載單例bean緩存 二級緩存
	//存放的 bean 不一定是完整的
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

getSingleton()的邏輯比較清晰:

  • 首先,嘗試從一級緩存singletonObjects中獲取單例Bean。

  • 如果獲取不到,則從二級緩存earlySingletonObjects中獲取單例Bean。

  • 如果仍然獲取不到,則從三級緩存singletonFactories中獲取單例BeanFactory。

  • 最后,如果從三級緩存中拿到了BeanFactory,則通過getObject()把Bean存入二級緩存中,并把該Bean的三級緩存刪掉。

2.1、三級緩存

看到這里可能會有些疑問,這3個緩存怎么就解決了singleton循環(huán)依賴了呢?
先別著急,我們現(xiàn)在分析了獲取緩存的代碼,再來看下存儲緩存的代碼。 在 AbstractAutowireCapableBeanFactorydoCreateBean() 方法中,有這么一段代碼:

// AbstractAutowireCapableBeanFactory.java

boolean earlySingletonExposure = (mbd.isSingleton() // 單例模式
        && this.allowCircularReferences // 允許循環(huán)依賴
        && isSingletonCurrentlyInCreation(beanName)); // 當(dāng)前單例 bean 是否正在被創(chuàng)建
if (earlySingletonExposure) {
    if (logger.isTraceEnabled()) {
        logger.trace("Eagerly caching bean '" + beanName +
                "' to allow for resolving potential circular references");
    }
    // 為了后期避免循環(huán)依賴,提前將創(chuàng)建的 bean 實例加入到三級緩存 singletonFactories 中
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}

這段代碼就是put三級緩存singletonFactories的地方,其核心邏輯是,當(dāng)滿足以下3個條件時,把bean加入三級緩存中:

  • 單例

  • 允許循環(huán)依賴

  • 當(dāng)前單例Bean正在創(chuàng)建

addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) 方法,代碼如下:

// DefaultSingletonBeanRegistry.java

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);
		}
	}
}

從這段代碼我們可以看出,singletonFactories 這個三級緩存才是解決 Spring Bean 循環(huán)依賴的關(guān)鍵。同時這段代碼發(fā)生在 createBeanInstance(...) 方法之后,也就是說這個 bean 其實已經(jīng)被創(chuàng)建出來了,但是它還沒有完善(沒有進(jìn)行屬性填充和初始化),但是對于其他依賴它的對象而言已經(jīng)足夠了(已經(jīng)有內(nèi)存地址了,可以根據(jù)對象引用定位到堆中對象),能夠被認(rèn)出來了。

2.2、一級緩存

到這里我們發(fā)現(xiàn)三級緩存 singletonFactories 和 二級緩存 earlySingletonObjects 中的值都有出處了,那一級緩存在哪里設(shè)置的呢?在類 DefaultSingletonBeanRegistry 中,可以發(fā)現(xiàn)這個 addSingleton(String beanName, Object singletonObject) 方法,代碼如下:

// DefaultSingletonBeanRegistry.java

protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
	        //添加至一級緩存,同時從二級、三級緩存中刪除。
		this.singletonObjects.put(beanName, singletonObject);
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}

該方法是在 #doGetBean(...) 方法中,處理不同 scope 時,如果是 singleton調(diào)用的,如下圖所示:

Spring中怎么解決循環(huán)依賴 也就是說,一級緩存里面是完整的Bean。

小結(jié):

  • 一級緩存里面是完整的Bean,是當(dāng)一個Bean完全創(chuàng)建后才put

  • 三級緩存是不完整的BeanFactory,是當(dāng)一個Bean在new之后就put(沒有屬性填充、初始化)

  • 二級緩存是對三級緩存的易用性處理,只不過是通過getObject()方法從三級緩存的BeanFactory中取出Bean

總結(jié)

現(xiàn)在我們再來回顧下Spring解決單例循環(huán)依賴的方案:

  • Spring 在創(chuàng)建 bean 的時候并不是等它完全完成,而是在創(chuàng)建過程中將創(chuàng)建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 三級緩存中)。

  • 這樣,一旦下一個 bean 創(chuàng)建的時候需要依賴 bean ,則從三級緩存中獲取。

舉個栗子
比如我們團(tuán)隊里要報名參加活動,你不用上來就把你的生日、性別、家庭信息什么的全部填完,你只要先報個名字,統(tǒng)計下人數(shù)就行,之后再慢慢完善你的個人信息。

核心思想:提前暴露,先用著

最后來描述下就上面那個循環(huán)依賴 Spring 解決的過程:

  • 首先 A 完成初始化第一步并將自己提前曝光出來(通過 三級緩存 將自己提前曝光),在初始化的時候,發(fā)現(xiàn)自己依賴對象 B,此時就會去嘗試 get(B),這個時候發(fā)現(xiàn) B 還沒有被創(chuàng)建出來

  • 然后 B 就走創(chuàng)建流程,在 B 初始化的時候,同樣發(fā)現(xiàn)自己依賴 C,C 也沒有被創(chuàng)建出來

  • 這個時候 C 又開始初始化進(jìn)程,但是在初始化的過程中發(fā)現(xiàn)自己依賴 A,于是嘗試 get(A),這個時候由于 A 已經(jīng)添加至緩存中(三級緩存 singletonFactories ),通過 ObjectFactory 提前曝光,所以可以通過 ObjectFactory#getObject() 方法來拿到 A 對象,C 拿到 A 對象后順利完成初始化,然后將自己添加到一級緩存中

  • 回到 B ,B 也可以拿到 C 對象,完成初始化,A 可以順利拿到 B 完成初始化。到這里整個鏈路就已經(jīng)完成了初始化過程了

http://cmsblogs.com/?p=todo (小明哥)

看完上述內(nèi)容,你們對Spring中怎么解決循環(huán)依賴有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI