Spring 解決循環依賴源碼分析

什麼是循環依賴

循環依賴就是N個類之間循環嵌套引用,如A依賴B,B又依賴A,你中有我,我中有你。實例化A時發現須要B屬性,因而去實例化B,發現須要A屬性。。。若是Spring不對這種循環依賴進行處理程序就會無限執行,致使內存溢出、系統崩潰。java

循環依賴又分爲構造器循環依賴屬性循環依賴,因爲Spring不支持構造器循環依賴,會直接報錯,因此接下來只討論屬性循環依賴。緩存

Bean實例化步驟

Bean實例化能夠大體分爲三步bash

                                           

其中循環依賴發生在實例化注入屬性這兩個步驟裏。app

解決方案

實例化A時,將A這個並不完整的對象緩存起來,這樣當B實例化後,注入A的時候,可以從容器中獲取到A對象,完成初始化。最後將B對象注入到A中,A完成初始化。ide

Spring引入了三級緩存來解決循環依賴的問題ui

/** 一級緩存,緩存初始化完成的bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
/** 二級緩存,緩存原始bean(還未填充屬性),用於解決循環依賴 */
private final Map<String, Object> earlySingletonObjects = new HashMap(16);
/** 三級緩存,緩存bean工廠對象,用於解決循環依賴 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);複製代碼

當獲取單例bean時,會依次訪問一級緩存、二級緩存、三級緩存,緩存命中則返回。this

源碼解析

時序圖


getBean是從容器中獲取bean的入口方法,它裏面又調用了doGetBean方法,來看一下這個方法。spa

doGetBean

protected  T doGetBean(final String name, @Nullable final Class 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;
}複製代碼

這個方法裏面有兩個名稱爲getSingleton的方法,第一個getSingleton是從緩存中查找bean,若是緩存未命中,則走第二個getSingleton,嘗試建立目標對象並注入依賴code

當第一次調用doGetBean獲取A對象,第一個getSingleton返回空,進入第二個getSingleton建立A對象,注入B對象。調用doGetBean獲取B,第一個getSingleton返回空,進入第二個getSingleton建立B對象,獲取並注入原始A對象,此時B對象初始化完成。最後將B對象注入A中,A完成初始化。cdn

先看第一個getSingleton的源碼

getSingleton(String beanName, boolean allowEarlyReference)

@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;
}複製代碼

首先從一級緩存singletonObjects獲取目標對象,若不存在且目標對象被標記爲建立中,從二級緩存earlySingletonObjects中獲取bean。若是不存在,繼續訪問三級緩存singletonFactories,獲得bean工廠對象,經過工廠對象獲取目標對象。將目標對象放入二級緩存,刪除三級緩存

第二個getSingleton方法

getSingleton(String beanName, ObjectFactory<?> singletonFactory)

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {

        // ......
        
        // 調用 getObject 方法建立 bean 實例
        singletonObject = singletonFactory.getObject();
        newSingleton = true;

        if (newSingleton) {
            // 添加 bean 到 singletonObjects 緩存中,並從其餘集合中將 bean 相關記錄移除
            addSingleton(beanName, singletonObject);
        }

        // ......
        
        // 返回 singletonObject
        return (singletonObject != NULL_OBJECT ? singletonObject : null);
    }
}複製代碼

該方法的主要邏輯是調用singletonFactory的getObject()方法建立目標對象,而後將bean放入緩存中。

接下來看一下getObject方法的實現,因爲在doGetBean中調用getSingleton時第二個參數是用匿名內部類的方式傳參,實際上調用的是createBean方法,而createBean又調用了doCreateBean

doCreateBean

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

    BeanWrapper instanceWrapper = null;

    // ......

    // ☆ 建立 bean 對象,並將 bean 對象包裹在 BeanWrapper 對象中返回
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    
    // 從 BeanWrapper 對象中獲取 bean 對象,這裏的 bean 指向的是一個原始的對象
    final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);

    /*
     * earlySingletonExposure 用於表示是否」提早暴露「原始對象的引用,用於解決循環依賴。
     * 對於單例 bean,該變量通常爲 true。更詳細的解釋能夠參考我以前的文章
     */ 
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
            isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        // ☆ 添加 bean 工廠對象到 singletonFactories 緩存中
        addSingletonFactory(beanName, new ObjectFactory<Object>() {
            @Override
            public Object getObject() throws BeansException {
                /* 
                 * 獲取原始對象的早期引用,在 getEarlyBeanReference 方法中,會執行 AOP 
                 * 相關邏輯。若 bean 未被 AOP 攔截,getEarlyBeanReference 原樣返回 
                 * bean,因此你們能夠把 
                 *      return getEarlyBeanReference(beanName, mbd, bean) 
                 * 等價於:
                 *      return bean;
                 */
                return getEarlyBeanReference(beanName, mbd, bean);
            }
        });
    }

    Object exposedObject = bean;

    // ......
    
    // ☆ 填充屬性,解析依賴
    populateBean(beanName, mbd, instanceWrapper);

    // ......

    // 返回 bean 實例
    return exposedObject;
}

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 將 singletonFactory 添加到 singletonFactories 緩存中
            this.singletonFactories.put(beanName, singletonFactory);

            // 從其餘緩存中移除相關記錄,即便沒有
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}複製代碼

方法的主要邏輯以下

  1. 用createBeanInstance建立目標對象
  2. 將對象添加到singletonFactories緩存
  3. populateBean注入依賴

總結

Spring在實例化bean時,會先建立當前bean對象,放入緩存中,而後以遞歸的方式獲取所依賴的屬性。當注入屬性時,若是出現了循環依賴則會從緩存中獲取依賴對象。

相關文章
相關標籤/搜索