Spring是怎麼解決循環依賴的?

在上篇文章中,咱們詳細分析了doCreateBean()中的第2步:實例化bean,本文接着分析doCreateBean()的第4步「循環依賴處理」,也就是populateBean()方法。java

首先回顧下Bean加載的主流程:緩存

  1. 若是是單例模式,從factoryBeanInstanceCache 緩存中獲取BeanWrapper 實例對象並刪除緩存
  2. 調用 createBeanInstance() 實例化 bean
  3. 後置處理
  4. 單例模式的循環依賴處理
  5. 屬性填充
  6. 初始化 bean 實例對象
  7. 依賴檢查
  8. 註冊 DisposableBean

本章咱們主要分析第4步:app

1、循環依賴是什麼?

循環依賴,其實就是循環引用,就是兩個或者兩個以上的 bean 互相引用對方,最終造成一個閉環,如 A 依賴 B,B 依賴 C,C 依賴 A。以下圖所示:源碼分析

Spring中的循環依賴,其實就是一個死循環的過程,在初始化 A 的時候發現依賴了 B,這時就會去初始化 B,而後又發現 B 依賴 C,跑去初始化 C,初始化 C 的時候發現依賴了 A,則又會去初始化 A,依次循環永不退出,除非有終結條件。post

通常來講,Spring 循環依賴的狀況有兩種:this

構造器的循環依賴。 field 屬性的循環依賴。 對於構造器的循環依賴,Spring 是沒法解決的,只能拋出 BeanCurrentlyInCreationException 異常表示循環依賴,因此下面咱們分析的都是基於 field 屬性的循環依賴。prototype

在前文 Spring Ioc源碼分析 之 Bean的加載(三):各個 scope 的 Bean 建立 中提到,Spring 只解決 scope 爲 singleton 的循環依賴。對於scope 爲 prototype 的 bean ,Spring 沒法解決,直接拋出 BeanCurrentlyInCreationException 異常。code

爲何 Spring 不處理 prototype bean 呢?其實若是理解 Spring 是如何解決 singleton bean 的循環依賴就明白了。這裏先留個疑問,咱們先來看下 Spring 是如何解決 singleton bean 的循環依賴的。對象

2、解決singleton循環依賴

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

//DefaultSingletonBeanRegistry.java

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 從一級緩存緩存 singletonObjects 中加載 bean
    Object singletonObject = this.singletonObjects.get(beanName);
    // 緩存中的 bean 爲空,且當前 bean 正在建立
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 加鎖
        synchronized (this.singletonObjects) {
            // 從 二級緩存 earlySingletonObjects 中獲取
            singletonObject = this.earlySingletonObjects.get(beanName);
            // earlySingletonObjects 中沒有,且容許提早建立
            if (singletonObject == null && allowEarlyReference) {
                // 從 三級緩存 singletonFactories 中獲取對應的 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個關鍵的變量,分別是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 */
	//預加載單例bean緩存 二級緩存
	//存放的 bean 不必定是完整的
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

getSingleton()的邏輯比較清晰:

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

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

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

  • 最後,若是從三級緩存中拿到了BeanFactory,則經過getObject()把Bean存入二級緩存中,並把該Bean的三級緩存刪掉。

2.一、三級緩存

看到這裏可能會有些疑問,這3個緩存怎麼就解決了singleton循環依賴了呢?
先彆着急,咱們如今分析了獲取緩存的代碼,再來看下存儲緩存的代碼。 在 AbstractAutowireCapableBeanFactorydoCreateBean() 方法中,有這麼一段代碼:

// AbstractAutowireCapableBeanFactory.java

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

這段代碼就是put三級緩存singletonFactories的地方,其核心邏輯是,當知足如下3個條件時,把bean加入三級緩存中:

  • 單例
  • 容許循環依賴
  • 當前單例Bean正在建立

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 循環依賴的關鍵。同時這段代碼發生在 createBeanInstance(...) 方法以後,也就是說這個 bean 其實已經被建立出來了,可是它尚未完善(沒有進行屬性填充和初始化),可是對於其餘依賴它的對象而言已經足夠了(已經有內存地址了,能夠根據對象引用定位到堆中對象),可以被認出來了。

2.二、一級緩存

到這裏咱們發現三級緩存 singletonFactories 和 二級緩存 earlySingletonObjects 中的值都有出處了,那一級緩存在哪裏設置的呢?在類 DefaultSingletonBeanRegistry 中,能夠發現這個 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調用的,以下圖所示:

也就是說,一級緩存裏面是完整的Bean。

小結:

  • 一級緩存裏面是完整的Bean,是當一個Bean徹底建立後才put
  • 三級緩存是不完整的BeanFactory,是當一個Bean在new以後就put(沒有屬性填充、初始化)
  • 二級緩存是對三級緩存的易用性處理,只不過是經過getObject()方法從三級緩存的BeanFactory中取出Bean
總結

如今咱們再來回顧下Spring解決單例循環依賴的方案:

  • Spring 在建立 bean 的時候並非等它徹底完成,而是在建立過程當中將建立中的 bean 的 ObjectFactory 提早曝光(即加入到 singletonFactories 三級緩存中)。

  • 這樣,一旦下一個 bean 建立的時候須要依賴 bean ,則從三級緩存中獲取。

舉個栗子
好比咱們團隊裏要報名參加活動,你不用上來就把你的生日、性別、家庭信息什麼的所有填完,你只要先報個名字,統計下人數就行,以後再慢慢完善你的我的信息。

核心思想:提早暴露,先用着

最後來描述下就上面那個循環依賴 Spring 解決的過程:

  • 首先 A 完成初始化第一步並將本身提早曝光出來(經過 三級緩存 將本身提早曝光),在初始化的時候,發現本身依賴對象 B,此時就會去嘗試 get(B),這個時候發現 B 尚未被建立出來

  • 而後 B 就走建立流程,在 B 初始化的時候,一樣發現本身依賴 C,C 也沒有被建立出來

  • 這個時候 C 又開始初始化進程,可是在初始化的過程當中發現本身依賴 A,因而嘗試 get(A),這個時候因爲 A 已經添加至緩存中(三級緩存 singletonFactories ),經過 ObjectFactory 提早曝光,因此能夠經過 ObjectFactory#getObject() 方法來拿到 A 對象,C 拿到 A 對象後順利完成初始化,而後將本身添加到一級緩存中

  • 回到 B ,B 也能夠拿到 C 對象,完成初始化,A 能夠順利拿到 B 完成初始化。到這裏整個鏈路就已經完成了初始化過程了

最後,爲何多例模式不能解決循環依賴呢?
由於多例模式下每次new() Bean都不是一個,若是按照這樣存到緩存中,就變成單例了。

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

相關文章
相關標籤/搜索