Spring框架是怎麼解決Bean之間的循環依賴的 (轉)

問題:面試

  循環依賴其實就是循環引用,也就是兩個或則兩個以上的bean互相持有對方,最終造成閉環。好比A依賴於B,B依賴於C,C又依賴於A。以下圖:spring

 

如何理解「依賴」呢,在Spring中有:緩存

  • 構造器循環依賴
  • field屬性注入循環依賴

直接上代碼:app

構造器循環依賴函數

@Service
public class A {  
    public A(B b) {  }
}

@Service
public class B {  
    public B(C c) {  
    }
}

@Service
public class C {  
    public C(A a) {  }
}

結果:項目啓動失敗,發現了一個cycleui

 

 2.field屬性注入循環依賴this

@Service
public class A1 {  
    @Autowired  
    private B1 b1;
}

@Service
public class B1 {  
    @Autowired  
    public C1 c1;
}

@Service
public class C1 {  
    @Autowired  public A1 a1;
}

結果:項目啓動成功spa

 

3.field屬性注入循環依賴(prototype).net

@Service
@Scope("prototype")
public class A1 {  
    @Autowired  
    private B1 b1;
}

@Service
@Scope("prototype")
public class B1 {  
    @Autowired  
    public C1 c1;
}

@Service
@Scope("prototype")
public class C1 {  
    @Autowired  public A1 a1;
}

結果:項目啓動失敗,發現了一個cycle。prototype

 
 

現象總結:一樣對於循環依賴的場景,構造器注入和prototype類型的屬性注入都會初始化Bean失敗。由於@Service默認是單例的,因此單例的屬性注入是能夠成功的。

 

Spring如何解決循環依賴

spring中循環依賴有三種狀況:

  一、構造器注入造成的循環依賴。也就是beanB須要在beanA的構造函數中完成初始化,beanA也須要在beanB的構造函數中完成溫馨化,這種狀況的結果就是兩個bean都不能完成初始化,循環依賴難以解決。

  二、setter注入構成的循環依賴。beanA須要在beanB的setter方法中完成初始化,beanB也須要在beanA的setter方法中完成初始化,spring設計的機制主要就是解決這種循環依賴,也是今天下文討論的重點。

  三、prototype做用域bean的循環依賴。這種循環依賴一樣沒法解決,由於spring不會緩存‘prototype’做用域的bean,而spring中循環依賴的解決正是經過緩存來實現的。

下面主要說明第二種狀況中循環依賴的解決方案

  步驟一:beanA進行初始化,而且將本身進行初始化的狀態記錄下來,並提早向外暴露一個單例工程方法,從而使其餘bean能引用到該bean(可能讀完這一句,您仍然心存疑惑,不要緊,繼續往下讀)

  步驟二:beanA中有beanB的依賴,因而開始初始化beanB。

  步驟三:初始化beanB的過程當中又發現beanB依賴了beanA,因而又進行beanA的初始化,這時發現beanA已經在進行初始化了,程序發現了存在的循環依賴,而後經過步驟一中暴露的單例工程方法拿到beanA的引用(注意,此時的beanA只是完成了構造函數的注入但爲完成其餘步驟),從而beanB拿到beanA的引用,完成注入,完成了初始化,如此beanB的引用也就能夠被beanA拿到,從而beanA也就完成了初始化。

  spring進行bean的加載的時候,首先進行bean的初始化(調用構造函數),而後進行屬性填充。在這兩步中間,spring對bean進行了一次狀態的記錄,也就是說spring會把指向只完成了構造函數初始化的bean的引用經過一個變量記錄下來,明白這一點對以後的源碼理解相當重要。

源碼角度觀看循環依賴的解決步驟

步驟一中首先進行beanA的建立

if (mbd.isSingleton()) {
    sharedInstance = getSingleton(beanName, () -> {
    try {
        return createBean(beanName, mbd, args);
    }
    catch (BeansException ex) {
        // Explicitly remove instance from singleton cache: It might have been put there
        // eagerly by the creation process, to allow for circular reference resolution.
        // Also remove any beans that received a temporary reference to the bean.
        destroySingleton(beanName);
        throw ex;
    }
});

進入getSingleton中,spirng會記錄當前beanA正在建立中

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

而且將註冊一個工廠方法來解決循環依賴

boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
            if (logger.isDebugEnabled()) {
                logger.debug("Eagerly caching bean '" + beanName +
                        "' to allow for resolving potential circular references");
            }
     這裏:    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        }

主要就是addSingletonFactory,這句就完成了工廠方法的註冊,這個方法能夠返回一個只完成了構造函數初始化的beanA,也許你們想知道他是如何返回的,咱們進入getEarlyBeanReference方法

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
        Object exposedObject = bean;
        if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                    SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
                    exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
                }
            }
        }
        return exposedObject;
    }

  能夠看到這個方法中,除了對後處理器的調用,沒有進行任何動做,而是直接返回了咱們參數傳入的bean,那麼這個bean是哪來的呢?其實在這以前,spring先調用了beanA的構造函數,並拿到了只完成了構造函數初始化的一個實例,並把他記錄了下來。

if (mbd.isSingleton()) {
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        final Object bean = instanceWrapper.getWrappedInstance();

  這就是這個bean的由來。而後當咱們進行到步驟三的時候,就會檢查是否容許循環依賴(即便是Singleton類型的bean也能夠經過參數設置,禁止循環依賴),若是容許的話,就會經過這個工廠方法拿到beanA的引用。從而完成beanA和beanB的加載。

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

 

也能夠看這篇 : Spring如何解決循環依賴的問題

 

  在關於Spring的面試中,咱們常常會被問到一個問題,就是Spring是如何解決循環依賴的問題的。這個問題算是關於Spring的一個高頻面試題,由於若是不刻意研讀,相信即便讀過源碼,面試者也不必定可以一會兒思考出箇中奧祕。本文主要針對這個問題,從源碼的角度對其實現原理進行講解。

1. 過程演示

        關於Spring bean的建立,其本質上仍是一個對象的建立,既然是對象,讀者朋友必定要明白一點就是,一個完整的對象包含兩部分:當前對象實例化和對象屬性的實例化。在Spring中,對象的實例化是經過反射實現的,而對象的屬性則是在對象實例化以後經過必定的方式設置的。這個過程能夠按照以下方式進行理解:

        理解這一個點以後,對於循環依賴的理解就已經幫助一大步了,咱們這裏以兩個類A和B爲例進行講解,以下是A和B的聲明:

@Component
public class A {

  private B b;

  public void setB(B b) {
    this.b = b;
  }
}
@Component
public class B {

  private A a;

  public void setA(A a) {
    this.a = a;
  }
}

        能夠看到,這裏A和B中各自都以對方爲本身的全局屬性。這裏首先須要說明的一點是,Spring實例化bean是經過ApplicationContext.getBean()方法來進行的。若是要獲取的對象依賴了另外一個對象,那麼其首先會建立當前對象,而後經過遞歸的調用ApplicationContext.getBean()方法來獲取所依賴的對象,最後將獲取到的對象注入到當前對象中。這裏咱們以上面的首先初始化A對象實例爲例進行講解。首先Spring嘗試經過ApplicationContext.getBean()方法獲取A對象的實例,因爲Spring容器中尚未A對象實例,於是其會建立一個A對象,而後發現其依賴了B對象,於是會嘗試遞歸的經過ApplicationContext.getBean()方法獲取B對象的實例,可是Spring容器中此時也沒有B對象的實例,於是其仍是會先建立一個B對象的實例。讀者須要注意這個時間點,此時A對象和B對象都已經建立了,而且保存在Spring容器中了,只不過A對象的屬性b和B對象的屬性a都尚未設置進去。在前面Spring建立B對象以後,Spring發現B對象依賴了屬性A,於是此時仍是會嘗試遞歸的調用ApplicationContext.getBean()方法獲取A對象的實例,由於Spring中已經有一個A對象的實例,雖然只是半成品(其屬性b還未初始化),但其也仍是目標bean,於是會將該A對象的實例返回。此時,B對象的屬性a就設置進去了,而後仍是ApplicationContext.getBean()方法遞歸的返回,也就是將B對象的實例返回,此時就會將該實例設置到A對象的屬性b中。這個時候,注意A對象的屬性b和B對象的屬性a都已經設置了目標對象的實例了。讀者朋友可能會比較疑惑的是,前面在爲對象B設置屬性a的時候,這個A類型屬性仍是個半成品。可是須要注意的是,這個A是一個引用,其本質上仍是最開始就實例化的A對象。而在上面這個遞歸過程的最後,Spring將獲取到的B對象實例設置到了A對象的屬性b中了,這裏的A對象其實和前面設置到實例B中的半成品A對象是同一個對象,其引用地址是同一個,這裏爲A對象的b屬性設置了值,其實也就是爲那個半成品的a屬性設置了值。下面咱們經過一個流程圖來對這個過程進行講解:

        圖中getBean()表示調用Spring的ApplicationContext.getBean()方法,而該方法中的參數,則表示咱們要嘗試獲取的目標對象。圖中的黑色箭頭表示一開始的方法調用走向,走到最後,返回了Spring中緩存的A對象以後,表示遞歸調用返回了,此時使用綠色的箭頭表示。從圖中咱們能夠很清楚的看到,B對象的a屬性是在第三步中注入的半成品A對象,而A對象的b屬性是在第二步中注入的成品B對象,此時半成品的A對象也就變成了成品的A對象,由於其屬性已經設置完成了。

2. 源碼講解

        對於Spring處理循環依賴問題的方式,咱們這裏經過上面的流程圖其實很容易就能夠理解,須要注意的一個點就是,Spring是如何標記開始生成的A對象是一個半成品,而且是如何保存A對象的。這裏的標記工做Spring是使用ApplicationContext的屬性Set<String> singletonsCurrentlyInCreation來保存的,而半成品的A對象則是經過Map<String, ObjectFactory<?>> singletonFactories來保存的,這裏的ObjectFactory是一個工廠對象,可經過調用其getObject()方法來獲取目標對象。在AbstractBeanFactory.doGetBean()方法中獲取對象的方法以下:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
    @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
  // 嘗試經過bean名稱獲取目標bean對象,好比這裏的A對象
  Object sharedInstance = getSingleton(beanName);
  
  // 咱們這裏的目標對象都是單例的
  if (mbd.isSingleton()) {
    // 這裏就嘗試建立目標對象,第二個參數傳的就是一個ObjectFactory類型的對象,這裏是使用Java8的lamada
    // 表達式書寫的,只要上面的getSingleton()方法返回值爲空,則會調用這裏的getSingleton()方法來建立
    // 目標對象
    sharedInstance = getSingleton(beanName, () -> {
      try {
        // 嘗試建立目標對象
        return createBean(beanName, mbd, args);
      } catch (BeansException ex) {
        throw ex;
      }
    });
  }
  return (T) bean;
}

        這裏的doGetBean()方法是很是關鍵的一個方法(中間省略了其餘代碼),上面也主要有兩個步驟,第一個步驟的getSingleton()方法的做用是嘗試從緩存中獲取目標對象,若是沒有獲取到,則嘗試獲取半成品的目標對象;若是第一個步驟沒有獲取到目標對象的實例,那麼就進入第二個步驟,第二個步驟的getSingleton()方法的做用是嘗試建立目標對象,而且爲該對象注入其所依賴的屬性。

        這裏其實就是主幹邏輯,咱們前面圖中已經標明,在整個過程當中會調用三次doGetBean()方法,第一次調用的時候會嘗試獲取A對象實例,此時走的是第一個getSingleton()方法,因爲沒有已經建立的A對象的成品或半成品,於是這裏獲得的是null,而後就會調用第二個getSingleton()方法,建立A對象的實例,而後遞歸的調用doGetBean()方法,嘗試獲取B對象的實例以注入到A對象中,此時因爲Spring容器中也沒有B對象的成品或半成品,於是仍是會走到第二個getSingleton()方法,在該方法中建立B對象的實例,建立完成以後,嘗試獲取其所依賴的A的實例做爲其屬性,於是仍是會遞歸的調用doGetBean()方法,此時須要注意的是,在前面因爲已經有了一個半成品的A對象的實例,於是這個時候,再嘗試獲取A對象的實例的時候,會走第一個getSingleton()方法,在該方法中會獲得一個半成品的A對象的實例。而後將該實例返回,而且將其注入到B對象的屬性a中,此時B對象實例化完成。而後將實例化完成的B對象遞歸的返回,此時就會將該實例注入到A對象中,這樣就獲得了一個成品的A對象。咱們這裏能夠閱讀上面的第一個getSingleton()方法:

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
  // 嘗試從緩存中獲取成品的目標對象,若是存在,則直接返回
  Object singletonObject = this.singletonObjects.get(beanName);
  // 若是緩存中不存在目標對象,則判斷當前對象是否已經處於建立過程當中,在前面的講解中,第一次嘗試獲取A對象
  // 的實例以後,就會將A對象標記爲正在建立中,於是最後再嘗試獲取A對象的時候,這裏的if判斷就會爲true
  if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
    synchronized (this.singletonObjects) {
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null && allowEarlyReference) {
        // 這裏的singletonFactories是一個Map,其key是bean的名稱,而值是一個ObjectFactory類型的
        // 對象,這裏對於A和B而言,調用圖其getObject()方法返回的就是A和B對象的實例,不管是不是半成品
        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
        if (singletonFactory != null) {
          // 獲取目標對象的實例
          singletonObject = singletonFactory.getObject();
          this.earlySingletonObjects.put(beanName, singletonObject);
          this.singletonFactories.remove(beanName);
        }
      }
    }
  }
  return singletonObject;
}

        這裏咱們會存在一個問題就是A的半成品實例是如何實例化的,而後是如何將其封裝爲一個ObjectFactory類型的對象,而且將其放到上面的singletonFactories屬性中的。這主要是在前面的第二個getSingleton()方法中,其最終會經過其傳入的第二個參數,從而調用createBean()方法,該方法的最終調用是委託給了另外一個doCreateBean()方法進行的,這裏面有以下一段代碼:

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

  // 實例化當前嘗試獲取的bean對象,好比A對象和B對象都是在這裏實例化的
  BeanWrapper instanceWrapper = null;
  if (mbd.isSingleton()) {
    instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  }
  if (instanceWrapper == null) {
    instanceWrapper = createBeanInstance(beanName, mbd, args);
  }
  
  // 判斷Spring是否配置了支持提早暴露目標bean,也就是是否支持提早暴露半成品的bean
  boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences 
    && isSingletonCurrentlyInCreation(beanName));
  if (earlySingletonExposure) {
    // 若是支持,這裏就會將當前生成的半成品的bean放到singletonFactories中,這個singletonFactories
    // 就是前面第一個getSingleton()方法中所使用到的singletonFactories屬性,也就是說,這裏就是
    // 封裝半成品的bean的地方。而這裏的getEarlyBeanReference()本質上是直接將放入的第三個參數,也就是
    // 目標bean直接返回
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
  }

  try {
    // 在初始化實例以後,這裏就是判斷當前bean是否依賴了其餘的bean,若是依賴了,
    // 就會遞歸的調用getBean()方法嘗試獲取目標bean
    populateBean(beanName, mbd, instanceWrapper);
  } catch (Throwable ex) {
    // 省略...
  }
  
  return exposedObject;
}

        到這裏,Spring整個解決循環依賴問題的實現思路已經比較清楚了。對於總體過程,讀者朋友只要理解兩點:

  • Spring是經過遞歸的方式獲取目標bean及其所依賴的bean的;
  • Spring實例化一個bean的時候,是分兩步進行的,首先實例化目標bean,而後爲其注入屬性。

        結合這兩點,也就是說,Spring在實例化一個bean的時候,是首先遞歸的實例化其所依賴的全部bean,直到某個bean沒有依賴其餘bean,此時就會將該實例返回,而後反遞歸的將獲取到的bean設置爲各個上層bean的屬性的。

3. 小結

        本文首先經過圖文的方式對Spring是如何解決循環依賴的問題進行了講解,而後從源碼的角度詳細講解了Spring是如何實現各個bean的裝配工做的。

 

 

出處:

  https://cloud.tencent.com/developer/article/1455953

  https://www.jianshu.com/p/8bb67ca11831

相關文章
相關標籤/搜索