從源碼分析Spring是如何解決循環依賴的

循環依賴問題

什麼是循環依賴

首先看一下下面的Spring配置文件java

<!-- beanA依賴於beanB -->
<bean id="beanA" class="top.okay3r.ClassA">
    <property name="beanB" ref="beanB"/>
</bean>
<!-- beanB依賴於beanA -->
<bean id="beanB" class="top.okay3r.ClassB">
    <property name="beanA" ref="beanA"/>
</bean>
複製代碼

當IOC容器讀取上面的配置時,就會先對beanA進行加載;在對beanA進行屬性填充時,會發現beanA依賴於beanB,而後就會對beanB進行加載;當對beanB進行屬性填充時,又會發現beanB依賴於beanA,因而就加載beanA... 能夠想到,若是Spring的容器對於這種循環依賴問題不做出響應的處理,那麼就會無限執行上面的過程。最終的結果就可能形成OOM從而致使程序崩潰 spring

WX20200213-173232@2x.png

Spring中bean注入的方式

咱們知道在Spring中,注入bean的方式有【構造器注入】和【setter注入】兩種方式。但在咱們使用Spring管理bean時,可能會遇到一種特殊的狀況,那麼就是上面所說的循環依賴問題 咱們再看一下Spring建立bean的過程緩存

Spring建立bean的過程

若是閱讀過IOC相關的源碼就會知道,建立bean的過程大致能夠分爲初始化bean對bean的屬性進行填充對bean進行初始化三個步驟app

  • 初始化bean:即new一個bean實例,是經過反射調用構造器實現的
  • 對bean的屬性進行填充:能夠理解爲對標籤相應的屬性進行賦值
  • 對bean進行初始化:即調用事先配置好的init-method方法,因此能夠將一些初始化的行爲寫到這個方法中

而後就來分析一下兩種注入方式ui

構造器注入

在普通的java程序中,若是已經new出了一個對象,咱們就知道這個對象已是可用的了,不論它的屬性是否完整。 但在Spring中,建立出來的bean必需要完成三個步驟才能被認爲是可用的,纔會將這個「完整」的bean放入到IOC容器中。 由於構造器注入是在實例化對象時反射調用構造器去注入參數,因此既然beanA、beanB的都拿不到完整的依賴,就會進行無限的循環調用,從而沒法解決【循環依賴問題】。解決辦法就只有是修改依賴關係了this

setter注入

再看一下setter注入方式 setter注入方式就是new出一個對象後,調用該對象的set方法對屬性進行賦值。此時對象已經被new出來了,只不過是不完整而已。 若是出現了循環依賴的問題,這就要比構造器注入的方式好的多 因此Spring對於循環依賴問題的解決就是針對於setter方法的spa

接下來就開始分析Spring是如何解決循環依賴問題的3d

Spring對於循環依賴的解決

先提早知道一下問題大概是怎樣解決的

首先咱們要知道,Spring對於循環依賴的問題是採用【緩存】的方式解決的 看一下Spring源碼中的DefaultSingletonBeanRegistry類(注:SingletonBeanRegistry接口提供了關於訪問單例bean的功能,DefaultSingletonBeanRegistry就是該接口的默認實現)代理

/** Cache of singleton objects: bean name to bean instance. */
    // 用於存儲完整的bean,接下來稱之爲【一級緩存】
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of early singleton objects: bean name to bean instance. */
    // 用於存儲不完整的bean,即只是new出來,並無屬性值的bean,接下來稱之爲【二級緩存】
	private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

    /** Cache of singleton factories: bean name to ObjectFactory. */
    //用於存儲bean工廠對象,接下來稱之爲【三級緩存】
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

複製代碼

大概捋一遍bean的獲取、建立過程

由於循環依賴都是產生在獲取bean時,因此咱們直接從AbstractBeanFactory的getBean()方法開始code

  1. AbstractBeanFactory#getBean()沒什麼自身的實現,只調用了doGetBean()
  2. AbstractBeanFactory#doGetBean(),在這個方法中調用了getSingleton(beanName)獲取實例:Object sharedInstance = getSingleton(beanName);
  3. 判斷sharedInstance是否爲null,若是不爲null則調用getObjectForBeanInstance處理,而後返回。也就是IOC容器獲取bean成功,能夠拿去使用了。若是sharedInstance爲null,則調用getSingleton(beanName,Object{...})方法
  4. DefaultSingletonBeanRegistry#getSingleton中,首先會從【一級緩存】中get一下bean,若是獲取不到,則會進入建立bean的流程
  5. 建立bean的主要邏輯就是走AbstractAutowireCapableBeanFactory#doCreateBean,先是使用createBeanInstance方法建立bean的實例,而後對bean進行初始化,再進行屬性填充....而後返回bean
  6. 獲取到bean,完成

上面並無涉及到循環依賴和二級、三級緩存的問題,由於對於循環依賴的處理,都表如今代碼中的細節之處

對應上面的過程,從源碼上開始分析

首先看doGetBean方法

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException{
		// 從緩存中獲取單例bean
		Object sharedInstance = getSingleton(beanName);
		if (sharedInstance != null && args == null) { //若是獲取到單例bean,則走下面代碼
            //......
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}else {//若是沒有獲取到單例bean,則走下面代碼 
                //...... 
				// 若是是單例的Bean,請下面的代碼
				if (mbd.isSingleton()) {
					sharedInstance = getSingleton(beanName, () -> {
						try {
							// 建立單例Bean的主要方法,返回的bean是完整的
							return createBean(beanName, mbd, args);
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
                //......
        }
		return (T) bean;
	}
}
複製代碼

上面的代碼中,sharedInstance是經過getSingleton()方法得到的,實際上getSingleton(beanName)方法沒什麼邏輯,內部調用了getSingleton(beanName, boolean)這個方法,因此接下來就進入到這個方法中

getSingleton(beanName, boolean)的實現

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		// 從一級緩存中獲取單例對象
		Object singletonObject = this.singletonObjects.get(beanName);
		// isSingletonCurrentlyInCreation : 判斷當前單例bean是否正在建立中,也就是沒有初始化完成。好比beanA的構造器依賴了beanB對象因此得先去建立B對象,或者在A的populateBean過程當中依賴了B對象,得先去建立B對象,這時的beanA就是處於建立中的狀態
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				// 從二級緩存中獲取單例bean
				singletonObject = this.earlySingletonObjects.get(beanName);
				// allowEarlyReference :是否容許從singletonFactories中經過getObject拿到對象
				if (singletonObject == null && allowEarlyReference) {
					// 從三級緩存中獲取單例bean
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						// 經過單例工廠獲取單例bean
						singletonObject = singletonFactory.getObject();
						// 從三級緩存移動到了二級緩存,並移除singletonFactory
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}
複製代碼

從上面的代碼中能夠總結出如下幾點:

  1. 先從【一級緩存】中查找,有則直接返回
  2. 若是在【一級緩存】中獲取不到,而且對象正在建立中(beanName包含在singletonsCurrentlyInCreation),那麼就再從【二級緩存】中查找,有則直接返回
  3. 若是仍是獲取不到,且容許singletonFactories(allowEarlyReference=true)經過getObject()獲取,就從【三級緩存】中獲取(singletonFactory.getObject())。經過ObjectFactory獲取到的對象,是進行代理後的對象(假設有AOP)。將從【三級緩存】中獲取到的對象放到【二級緩存】中,同時刪除此beanName對應的【三級緩存數據】

再看一下doGetBean()方法中剛剛沒有講到的「if-else」部分

若是getSingleton()方法獲取到了bean,即sharedInstance不爲null,則對其進行處理而後返回 若是sharedInstance爲null,就要走else中的代碼了 首先判斷一下是否爲單例,(mbd是經過讀取配置文件中bean標籤生成的bean的定義信息,具體得到的方法這裏不詳細說了)。由於多例的bean是不須要放入到IOC容器中的,因此這裏只處理單例bean 若是爲單例,則調用getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法

getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        // ......
        // 建立 bean 實例
        singletonObject = singletonFactory.getObject();
        newSingleton = true;
        if (newSingleton) {
            // 添加新建立的bean添加到【一級緩存】中,並刪除其餘緩存中對應的bean
            addSingleton(beanName, singletonObject);
        }
        // ......
        // 返回 singletonObject
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}

protected void addSingleton(String beanName, Object singletonObject) {
    synchronized (this.singletonObjects) {
        // 將新建立的bean添加到【一級緩存】中
        this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));

        // 從其餘緩存中移除相關的bean
        this.singletonFactories.remove(beanName);
        this.earlySingletonObjects.remove(beanName);
        this.registeredSingletons.add(beanName);
    }
}
複製代碼

上面的代碼主要包含了兩個功能

  1. 獲取完整的bean實例
  2. 將新的bean添加到【一級緩存】中,之後getBean的時候就能夠直接獲取了

能夠看到bean實例是由singletonFactory.getObject()拿到的,也就是經過doGetBean()方法中判斷是否單例後的匿名內部類獲取到的,從而知道獲取到的bean是由createBean()方法建立的

creatBean()方法調用了doCreatBean()方法,因此實際的建立邏輯就再doCreatBean()中

doCreatBean()

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

        // 默認調用無參構造實例化Bean
		// 構造方法的依賴注入,就是發生在這一步
		if (instanceWrapper == null) {
			instanceWrapper = createBeanInstance(beanName, mbd, args);
		}
		// 實例化後的Bean對象,這裏獲取到的是一個原始對象,即沒有進行屬性填充的對象
		final Object bean = instanceWrapper.getWrappedInstance();
		Class<?> beanType = instanceWrapper.getWrappedClass();
		
        //......

		// 解決循環依賴的關鍵步驟
        // earlySingletonExposure:是否」提早暴露「原始對象的引用
        // 由於不論這個bean是否完整,他先後的引用都是同樣的,因此提早暴露的引用到後來也指向完整的bean
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		// 若是須要提早暴露單例bean,則將該bean工廠放入【三級緩存】中
		if (earlySingletonExposure) {
			// 將剛建立的bean工廠放入三級緩存中singleFactories(key是beanName,value是FactoryBean)
            // 一樣也會移除【二級緩存】中對應的bean,即使沒有
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
            //填充屬性(依賴注入)
			populateBean(beanName, mbd, instanceWrapper);
			//調用初始化方法,完成bean的初始化操做
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		//......

		return exposedObject;
	}
複製代碼

ok,看到這裏,整個在有循環依賴問題下建立、獲取bean的流程就結束了 舉個例子,從頭串一下流程。假設beanA->beanB, beanB->beanA,即A、B相互依賴

  1. 調用doGetBean()方法,想要獲取beanA,因而調用getSingleton()方法從緩存中查找beanA
  2. 在getSingleton()方法中,從一級緩存中查找,沒有,返回null
  3. doGetBean()方法中獲取到的beanA爲null,因而走對應的處理邏輯,調用getSingleton()的重載方法(參數爲ObjectFactory的) (ps:如今是2020.2.14 凌晨1點07分,情人節了,由於疫情不能和小楊一塊兒,在個人第一篇博客中記念一下這個節日😂,祝全部人情人節快樂)
  4. 在getSingleton()方法中,先將beanA_name添加到一個集合中,用於標記該bean正在建立中。而後回調匿名內部類的creatBean方法
  5. 進入AbstractAutowireCapableBeanFactory#doCreateBean,先反射調用構造器建立出beanA的實例,而後判斷:是否爲單例、是否容許提早暴露引用(對於單例通常爲true)、是否正在建立中(便是否在第四步的集合中)。判斷爲true,則將beanA添加到【三級緩存】中
  6. 對beanA進行屬性填充,此時檢測到beanA依賴於beanB,因而開始查找beanB
  7. 調用doGetBean()方法,和上面beanA的過程同樣,到緩存中查找beanB,沒有則建立,而後給beanB填充屬性
  8. 此時beanB依賴於beanA,調用getSingleton()獲取beanA,依次從一級、二級、三級緩存中找,此時從三級緩存中獲取到beanA的建立工廠,經過建立工廠獲取到singletonObject,此時這個singletonObject指向的就是上面在doCreateBean()方法中實例化的beanA
  9. 這樣beanB就獲取到了beanA的依賴,因而beanB順利完成實例化,並將beanA從三級緩存移動到二級緩存中
  10. 隨後beanA繼續他的屬性填充工做,此時也獲取到了beanB,beanA也隨之完成了建立,回到getSingleton()方法中繼續向下執行,將beanA從二級緩存移動到一級緩存中

最後

整個過程大概就是這樣了,因爲spring的源碼比較多,就只挑選了重點部分進行註釋 其實主要思想就是利用二級、三級緩存對未初始化完成的bean進行提早的引用暴露,也就是將其設置爲可引用的,這樣當依賴於他的bean在進行屬性填充時就能夠直接拿到引用,解決了死循環的問題

還有幾個比較重要的點,在這裏指出位置,能夠根據這些去查找看

  • 這三個級別的緩存,在同一時間,同一beanName對應的bean只會存在於一個緩存中
  • 若是沒有循環依賴的問題,二級、三級緩存是沒有用處的,體如今AbstractAutowireCapableBeanFactory#doCreateBean的判斷earlySingletonExposure這個地方
  • 判斷循環依賴,是用一個Set集合實現的,正在建立中的beanName會加到這個集合中
  • 三級緩存其實還有建立AOP代理的功能,在AbstractAutowireCapableBeanFactory#createBean調用resolveBeforeInstantiation的位置。而若是沒有循環依賴問題,那麼代理就是在調用init-method過程當中建立的
  • bean實例化以後,屬性填充以前,若是有循環依賴,就將這個bean封裝到一個ObjectFactory而後放到三級緩存中(爲了提早暴露引用)
  • 三級緩存中的ObjectFactory第一次拿出被他保存bean後,這個bean就會進入二級緩存
  • bean被建立完整後,進入一級緩存

》》》》》》》》》》》》》》》》》》》》》

有些東西不知道怎麼轉述成語言表達出來,還有若是有很差的或者說錯的地方但願看過的大佬能幫忙指正,謝謝~~

相關文章
相關標籤/搜索