Spring是如何解決循環依賴的

前言

在面試的時候這兩年有一個很是高頻的關於spring的問題,那就是spring是如何解決循環依賴的。這個問題聽着就是輕描淡寫的一句話,其實考察的內容仍是很是多的,主要仍是考察的應聘者有沒有研究過spring的源碼。可是說實話,spring的源碼其實很是複雜的,研究起來並非個簡單的事情,因此咱們此篇文章只是爲了解釋清楚Spring是如何解決循環依賴的這個問題。java

什麼樣的依賴算是循環依賴?

用過Spring框架的人都對依賴注入這個詞不陌生,一個Java類A中存在一個屬性是類B的一個對象,那麼咱們就說類A的對象依賴類B,而在Spring中是依靠的IOC來實現的對象注入,也就是說建立對象的過程是IOC容器來實現的,並不須要本身在使用的時候經過new關鍵字來建立對象。
那麼當類A中依賴類B的對象,而類B中又依賴類C的對象,最後類C中又依賴類A的對象的時候,這種狀況最終的依賴關係會造成一個環,這就是循環依賴。
在這裏插入圖片描述面試

循環依賴的類型

根據注入的時機能夠分爲兩種:spring

  • 構造器循環依賴
    依賴的對象是經過構造方法傳入的,在實例化bean的時候發生。
  • 賦值屬性循環依賴
    依賴的對象是經過setter方法傳入的,對象已經實例化,在屬性賦值和依賴注入的時候發生。
    構造器循環依賴,本質上是無解的,實例化A的時候調用A的構造器,發現依賴了B,又去實例化B,而後調用B的構造器,發現又依賴的C,而後調用C的構造器去實例化,結果發起C的構造器裏依賴了A,這就是個死循環無解。因此Spring也是不支持構造器循環依賴的,當發現存在構造器循環依賴時,會直接拋出BeanCurrentlyInCreationException 異常。
    賦值屬性循環依賴,Spring只支持bean在單例模式下的循環依賴,其餘模式下的循環依賴Spring也是會拋出BeanCurrentlyInCreationException 異常的。Spring經過對還在建立過程當中的單例bean,進行緩存並提早暴露該單例,使得其餘實例能夠提早引用到該單例bean。

Spring爲何只支持單例模式下的bean的賦值狀況下的循環依賴

在prototype的模式下的bean,使用了一個ThreadLocal變量prototypesCurrentlyInCreation來記錄當前線程正在建立中的bean,這個變量在AbtractBeanFactory類裏。在建立前用beanName記錄bean,在建立完成後刪除bean。在prototypesCurrentlyInCreation裏採用了一個Set對象來存儲正在建立中的bean。咱們都知道Set是不容許存在重複對象的,這樣就能保證同一個bean在一個線程中只能有一個正在建立。
下面是prototypesCurrentlyInCreation變量在刪除bean時的操做,在AbtractBeanFactorybeforePrototypeCreation操做裏。緩存

protected void afterPrototypeCreation(String beanName) {
      Object curVal = this.prototypesCurrentlyInCreation.get();
      if (curVal instanceof String) {
          this.prototypesCurrentlyInCreation.remove();
      }
      else if (curVal instanceof Set) {
          Set<String> beanNameSet = (Set<String>) curVal;
          beanNameSet.remove(beanName);
          if (beanNameSet.isEmpty()) {
              this.prototypesCurrentlyInCreation.remove();
          }
      }
  }

從上面的代碼中看出,當變量爲一個的時候採用了一個String對象來存儲,節省了一些內存空間。
AbstractBeanFactory類的doGetBean方法裏先判斷是否爲單例對象,不是單例對象,則直接判斷當前線程是否已經存在了正在建立的bean。存在的話直接拋出異常。
在這裏插入圖片描述
這個isPrototypeCurrentlyInCreation()方法的實現代碼以下:框架

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
        Object curVal = this.prototypesCurrentlyInCreation.get();
        return curVal != null && (curVal.equals(beanName) || curVal instanceof Set && ((Set)curVal).contains(beanName));
    }

由於有了這個機制,spring在原型模式下是解決不了bean的循環依賴的,當發現有循環依賴的時候會直接拋出BeanCurrentlyInCreationException異常的。this

那麼爲何spring在單例模式下的構造賦值也不支持循環依賴呢?

其實原理和原型模式下的狀況相似,在單例模式下,bean也會用一個Set集合來保存正在建立中的bean,在建立前保存,建立完成後刪除。
這個對象在DefaultSingletonBeanRegistry類下變量名爲:singletonsCurrentlyInCreationprototype

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	 private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
}

斷定代碼在DefaultSingletonBeanRegistry類的beforeSingletonCreation方法下。線程

protected void beforeSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
}

在上面這個方法中,斷定singletonsCurrentlyInCreation是否能成功的保存一個單例bean。若是不能成功保存,那麼就會直接拋出BeanCurrentlyInCreationException異常。code

單例模式下的Setter賦值循環依賴

終於到了咱們的重點,Spring是如何解決單例模式下的Setter賦值的循環依賴了。
其實主要的就是靠提早暴露建立中的單例實例
那麼具體是一個怎樣的過程呢?
例如:上面那個圖的例子,A依賴B,B依賴C,C又依賴B。
過程以下:對象

  • 建立A,調用構造方法,完成構造,進行屬性賦值注入,發現依賴B,去實例化B
  • 建立B,調用構造方法,完成構造,進行屬性賦值注入,發現依賴C,去實例化C
  • 建立C,調用構造方法,完成構造,進行屬性賦值注入,發現依賴A。
    這個時候就是解決循環依賴的關鍵了,由於A已經經過構造方法已經構造完成了,也就是說已經將Bean的在堆中分配好了內存,這樣即便A再填充屬性值也不會更改內存地址了,因此此時能夠提早拿出來A的引用,來完成C的實例化。
    這樣上面建立C過程就會變成了:
  • 建立C,調用構造方法,完成構造,進行屬性賦值注入,發現依賴A,A已經構造完成,直接引用,完成C的實例化
  • C完成實例化後,注入B,B也完成了實例化,而後B注入A,A也完成了實例化
    爲了能獲取到建立中單例bean,spring提供了三級緩存來將正在建立中的bean提早暴露。
    在類DefaultSingletonBeanRegistry下,即下圖紅框中的三個Map對象。
    在這裏插入圖片描述
    這三個緩存Map的做用以下:
  • 一級緩存,singletonObjects 單例緩存,存儲已經實例化的單例bean。
  • 二級緩存,earlySingletonObjects 提早暴露的單例緩存,這裏存儲的bean是剛剛構造完成,但還會經過屬性注入bean。
  • 三級緩存,singletonFactories 生產單例的工廠緩存,存儲工廠。

首先在建立bean的時候會先建立一個和bean同名的單例工廠,並將bean先放入到單例工廠中。代碼在AbstractAutowireCapableBeanFactory類的doCreateBean方法中。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
	......
	this.addSingletonFactory(beanName, new ObjectFactory<Object>() {
        public Object getObject() throws BeansException {
            return AbstractAutowireCapableBeanFactory.this.getEarlyBeanReference(beanName, mbd, bean);
        }
    });
	.....
}

而上面的代碼中的addSingletonFactory方法的代碼以下:

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    Map var3 = this.singletonObjects;
    synchronized(this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }

    }
}

addSingletonFactory方法的做用經過代碼就能夠看到是將存在了正在建立中的bean的單例工廠,放在三級緩存裏,這樣保證了在循環依賴查找的時候是能夠找到bean的引用的。
具體讀取緩存獲取bean的過程在類DefaultSingletonBeanRegistrygetSingleton方法裏。
以下源碼:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
     Object singletonObject = this.singletonObjects.get(beanName);
     if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
         Map var4 = this.singletonObjects;
         synchronized(this.singletonObjects) {
             singletonObject = this.earlySingletonObjects.get(beanName);
             if (singletonObject == null && allowEarlyReference) {
                 ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                 if (singletonFactory != null) {
                     singletonObject = singletonFactory.getObject();
                     this.earlySingletonObjects.put(beanName, singletonObject);
                     this.singletonFactories.remove(beanName);
                 }
             }
         }
     }

     return singletonObject != NULL_OBJECT ? singletonObject : null;
 }

經過上面的源碼咱們能夠看到,在獲取單例Bean的時候,會先從一級緩存singletonObjects裏獲取,若是沒有獲取到(說明不存在或沒有實例化完成),會去第二級緩存earlySingletonObjects中去找,若是仍是沒有找到的話,就會三級緩存中獲取單例工廠singletonFactory,經過從singletonFactory中獲取正在建立中的引用,將singletonFactory存儲在earlySingletonObjects 二級緩存中,這樣就將建立中的單例引用從三級緩存中升級到了二級緩存中,二級緩存earlySingletonObjects,是會提早暴露已完成構造,還能夠執行屬性注入的單例bean的。
這個時候如何還有其餘的bean也是須要屬性注入,那麼就能夠直接從earlySingletonObjects中獲取了。

上面的例子中的過程當中的A,在注入C的時候,其實並無真正的初始化完成,等到順利的注入了B纔算是真正的初始化完成。
整個過程以下圖:
在這裏插入圖片描述

相關文章
相關標籤/搜索