Spring原始碼分析之IOC迴圈依賴

理智.發表於2019-12-29

1.什麼是迴圈依賴?

當多個Bean相互依賴時則構成了迴圈依賴,例如A,B兩個Bean。其中A中存在屬性B,B中存在屬性A,當Spring在例項化A時發現A中存在屬性B,就去例項化B,例項化B時又發現存在屬性A,一直在迴圈注入依賴,導致迴圈依賴問題出現。

2.Spring是怎麼解決迴圈依賴的?

Spring中會通過各種Bean中間狀態來達到Bean還未例項化完成時提前將Bean提前注入到依賴Bean的屬性中,假設說Bean有三種狀態分別是青年態(一級快取)、胚胎態(二級快取)、小蝌蚪態(三級快取)其中青年態代表Bean已經例項化完成,可以直接使用了,胚胎態代表Bean已經存在了但是還在建立中,還未建立完畢,小蝌蚪態代表還未開始建立,但是隨時可以進行建立,三個狀態就類似於三個等級,可以逐步提升從小蝌蚪狀態提升到胚胎狀態然後再提升到青年態,然後Spring開始建立Bena時會提前將Bean存放到小蝌蚪態的快取集合中,當發現存在迴圈依賴時會使用存在於小蝌蚪狀態快取集合中的Bean,提前

3.迴圈依賴的案例

假設例子中存在BeanA、BeanB、BeanC、BeanD四個Bean,其中
  • BeanA依賴著BeanB,C
  • BeanB依賴著BeanC
  • BeanC依賴著BeanA
  • BeanD依賴著BeanA,B,C
BeanA beanA = beanFactory.getBean("beanA",BeanA.class); BeanB beanB = beanFactory.getBean("beanB",BeanB.class); BeanC beanC = beanFactory.getBean("beanC",BeanC.class); BeanD beanD = beanFactory.getBean("beanD",BeanD.class);
那麼他們的例項化流程是什麼樣的呢?
Spring原始碼分析之IOC迴圈依賴Spring原始碼分析之IOC迴圈依賴
發現了吧,Spring解決迴圈依賴的法寶就是快取。

3.程式碼解析(只保留相關程式碼)

1.檢查快取中是否已經存在例項化完畢的Bean

protected Object getSingleton(String beanName, boolean allowEarlyReference) {

   //首先檢查一級快取中是否存在
   Object singletonObject = this.singletonObjects.get(beanName);

   /**
    *  如果一級快取中不存在代表當前 Bean 還未被建立或者正在建立中
    *  檢查當前 Bean 是否正處於正在建立的狀態中(當Bean建立時會將Bean名稱存放到 singletonsCurrentlyInCreation 集合中)
   */
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
         //檢查二級快取中是否存在
         singletonObject = this.earlySingletonObjects.get(beanName);

         /**
          * @@如果二級快取中不存在 並且 允許使用早期依賴
          * allowEarlyReference : 它的含義是是否允許早期依賴
          * @@那麼什麼是早期依賴? 
          * 就是當Bean還未成為成熟的Bean時就提前使用它,在例項化流程圖中我們看到在新增快取前剛剛例項化Bean但是還未依賴注入時的狀態
         */
         if (singletonObject == null && allowEarlyReference) {
            
            //獲取三級快取中的 Bean ObjectFactory
            ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
            //如果 Bean 對應的 ObjectFactory 存在
            if (singletonFactory != null) {
               //使用 getObject 方法獲取到 Bean 的例項
               singletonObject = singletonFactory.getObject();
               //將 bean 從三級快取提升至二級快取
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}複製程式碼

2.建立Bean

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

   /**
    * 我們一開始通過 getSingleton() 方法中獲取三級快取中存放的Bean,這裡就是向三級快取中新增 bean 的地方
    * 流程:
    * 1.檢查當前 bean 是否為單例模式,並且是否允許迴圈引用[講解1],並且當前是否正在建立中(在getSingleton方法中新增的)
    * 2.如果允許提前曝光[講解2],addSingletonFactory() 方法向快取中新增當前 bean 的 ObjectFactory
    *
    * [講解1]:當前 Bean 如果不允許迴圈引用(迴圈依賴也就是被依賴),則這裡就不會提前曝光,對應的 ObjectFactory
    * 則當發生迴圈依賴時會丟擲 BeanCreationException 異常
    *
    * [講解2]:提前曝光的含義就是說當 bean 還未建立完畢時就先將建立中狀態的bean放到指定快取中,為迴圈依賴提供支援
   */
   boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
         isSingletonCurrentlyInCreation(beanName));
   //需要提前曝光
   if (earlySingletonExposure) {
      /**
       * 向快取(三級快取)中新增當前 bean 的 ObjectFactory
      */
      addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
   }

   /**
    * Initialize the bean instance.
    * 初始化 Bean 例項階段
   */
   Object exposedObject = bean;
   try {
      /**
       *  依賴注入這時會遞迴呼叫getBean
      */
      populateBean(beanName, mbd, instanceWrapper);
      //呼叫初始化方法,如:init-method
      exposedObject = initializeBean(beanName, exposedObject, mbd);
   }
   
   /**
    * 當允許提前曝光時進入判斷
    * @這裡做了什麼?
    * 1.檢查當前bean是否經歷了一場迴圈依賴
    *   - 通過 getSingleton(beanName,false) 獲取快取中的 bean,傳入 false 代表不獲取三級快取中的bean
    *   - 為什麼說 檢查當前bean是否經歷了一場迴圈依賴呢? 因為上述說了傳入 false 代表不獲取三級快取的
    *   - 那麼什麼情況下才會存在與一級快取和二級快取呢?答案就是迴圈依賴後 [解釋1] 和bean例項化完成後
    *   - 所以如果 getSingleton 返回的 bean 不為空,則這個bean就是剛剛經歷了迴圈依賴
    *
    * 2.檢查提前曝光的bean和當前的Bean是否一致
    *   - 下面有個判斷 if (exposedObject == bean) ,這個判斷從快取中獲取的bean 和 經歷過初始化後的 bean
    *   - 是否一致,可能我們有點暈,這裡解釋一下,快取從的bean是什麼時候存進去的?是在 addSingletonFactory 方法(649行)
    *   - 然後這裡存進去的 bean 只是提前曝光的 bean,還沒有依賴注入和初始化,但是在依賴注入和初始化時都是可能直接改變
    *   - 當前 bean 的例項的,這意味著什麼?意味著經歷了依賴注入和初始化的bean很可能和快取中的bean就已經完全不是一個 bean了
    *   下面講解當一致或不一致時的邏輯:
    * 2.1 一致:
    *  不是很理解,直接賦值,可是經歷了各種 BeanPostProsser 或者依賴注入和初始化後不是就不一樣了嗎
    * 2.2 不一致:
    *  看下方對於 else if 程式碼塊的解釋
    *
    * @[解釋1]
    * 當迴圈依賴時,A依賴著B,B依賴著A,例項化A首先將A放到三級快取中然後發現依賴著B,然後去例項化B,發現依賴著A
    * 發現A在三級快取,然後獲取三級快取中的bean並且將A從三級快取中提升到二級快取中,例項化B完成,接著例項化A也完成。
    *
    * @通俗講解
    * 假設我們業務上對某種資料加了快取,假設 i 在快取中存的值為1,當我在資料庫中把 i 的值改成 2 時,快取中的 i 還沒有被改變還是 1
    * 這時的資料已經和我們的真實資料偏離了,不一致了,這時有兩種解決方式:1.伺服器檢查到資料不一致丟擲異常。(也就是進入else if 程式碼塊)
    * 2.直接使用原始值也就是1(也就是將 allowRawInjectionDespiteWrapping 改成 true),當然這兩種方式明顯不是我們正常資料庫的操作,只是
    * 為了說明當前的這個例子而已。
    *
   */
   if (earlySingletonExposure) {
      //獲取快取中(除三級快取) beanName 對應的 bean
      Object earlySingletonReference = getSingleton(beanName, false);
      //當經歷了一場迴圈依賴後 earlySingletonReference 就不會為空
      if (earlySingletonReference != null) {
         //如果 exposedObject 沒有在初始化方法中被改變,也就是沒有被增強
         if (exposedObject == bean) {
            //直接賦值? 可是經歷了各種 BeanPostProsser 或者依賴注入和初始化後不是就不一樣了嗎
            exposedObject = earlySingletonReference;
         }
         /**
          *
          * 走到 else if  時說明 當前 Bean 被 BeanPostProessor 增強了
          * 判斷的條件為:
          * 1.如果允許使用被增強的
          * 2.檢查是否存在依賴當前bean的bean
          *
          * 如果存在依賴的bean已經被例項化完成的,如果存在則丟擲異常
          * 為什麼丟擲異常呢?
          * 因為依賴當前bean 的bean 已經在內部注入了當前bean的舊版本,但是通過初始化方法後這個bean的版本已經變成新的了
          * 舊的哪個已經不適用了,所以丟擲異常
          *
         */
         else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            String[] dependentBeans = getDependentBeans(beanName);
            Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
            for (String dependentBean : dependentBeans) {
               if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                  actualDependentBeans.add(dependentBean);
               }
            }
            if (!actualDependentBeans.isEmpty()) {
               throw new BeanCurrentlyInCreationException(beanName,
                     "Bean with name '" + beanName + "' has been injected into other beans [" +
                     StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
                     "] in its raw version as part of a circular reference, but has eventually been " +
                     "wrapped. This means that said other beans do not use the final version of the " +
                     "bean. This is often the result of over-eager type matching - consider using " +
                     "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
            }
         }
      }
   }

   try {
      /**
       * Register bean as disposable.
       * 註冊 Bean 的銷燬方法擴充
      */
      registerDisposableBeanIfNecessary(beanName, bean, mbd);
   }
   catch (BeanDefinitionValidationException ex) {
      throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
   }

   return exposedObject;
}複製程式碼

結束語

我也在學習過程中,如果發現文中有不對的地方請直接聯絡捶我就好,如果有興趣可以聯絡我我們一起學習!

Spring原始碼分析之IOC迴圈依賴


相關文章