Spring IoC - 迴圈依賴

Linus1發表於2021-02-27

Spring 複習

3.迴圈依賴

3.1 定義

迴圈依賴指多個物件的建立過程中均需要注入對方物件,如下所示

class A{
    B b;
    public A(){
    }
    public A(B b){
        this.b = b;
    }
    public void setB(B b){
        this.b = b;
    }
}
class B{
    A a;
    public B(){
    }
    public B(A a){
        this.a = a;
    }
    public void setA(A a){
        this.a = a;
    }
}

3.2 解決

Spring中將物件建立分為如下兩步

  • 例項化:建立初始物件
  • 初始化:注入屬性

並且引入三級快取,來提前暴露物件引用,從而解決迴圈依賴的問題

3.3 示例

假設A和B的建立中,field均需要對方的引用,在refresh方法進行到finishBeanFactoryInitialization(beanFactory)時,會開始建立非懶載入的singleton,這裡會先進入preInstantiateSingletons方法,根據beanName呼叫getBean方法,假設此時A先進行建立,那麼會進入下面方法

  • doGetBeanorg.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

    • getSingleton---1

      @Override
      @Nullable
      public Object getSingleton(String beanName) {
         return getSingleton(beanName, true);
      }
      
      @Nullable
      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;
      }
      

      首先呼叫上面方法先從singletonObjects中場是獲取,發現為null,由於isSingletonCurrentlyInCreation為false(物件未在建立過程中),因此直接返回null

      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;
            }
         });
         bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
      }
      

      其中執行DefaultSingletonBeanRegistry#getSingleton(beanName,ObjectFactory)方法簡化版如下,傳入的ObjectFactory實現類是一個lambda表示式,也即用createBean方法重寫ObjectFactory#getObject方法

      • getSingleton---2
      public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
         Assert.notNull(beanName, "Bean name must not be null");
         synchronized (this.singletonObjects) {
           	boolean newSingleton = false;
      
               try {
                  singletonObject = singletonFactory.getObject();
                  newSingleton = true;
               }
               catch (IllegalStateException ex) {
                  }
               }
               catch (BeanCreationException ex) {
               }
               finally {
                   
               }
               if (newSingleton) {
                  addSingleton(beanName, singletonObject);
               }
            }
            return singletonObject;
         }
      }
      

      這裡第一行呼叫singletonFactory.getObject方法會觸發createBean,又觸發AbstractAutowireCapableBeanFactory#doCreateBean方法中主題步驟如下

      • 例項化bean

      • 將bean放入三級快取singletonFactories

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

        其中呼叫addSingletonFactory方法如下,此處傳入的lambda表示式給定的即為ObjectFactory物件,在執行其getObject方法時,即執行getEarlyBeanReference方法(這裡需要留意!)

        protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
           Assert.notNull(singletonFactory, "Singleton factory must not be null");
           synchronized (this.singletonObjects) {
              if (!this.singletonObjects.containsKey(beanName)) {
                 this.singletonFactories.put(beanName, singletonFactory);
                 this.earlySingletonObjects.remove(beanName);
                 this.registeredSingletons.add(beanName);
              }
           }
        }
        
      • A執行populateBean,開始注入屬性b,由於B的物件還未建立,getSingleton---1(b)為null,這時觸發B物件建立

      • B進行例項化

      • B放入三級快取

      • B執行populateBean,開始注入屬性a,呼叫getSingleton---1方法獲取a,發現一級快取singletonObject中沒有對應物件,且正在建立中,則從二級快取earlySingletonObjects中獲取,發現仍然為null且allowEarlyReference預設為true,則去三級快取中去獲取,最終從三級快取中獲取,由於放入三級快取時,lambda表示式為() -> getEarlyBeanReference(beanName, mbd, bean),所以會呼叫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;
        }
        

        這裡在遍歷後置處理器的過程中,會呼叫到AbstractAutoProxyCreator的postProcessAfterInitialization方法,此方法會判斷A是否被代理,如果被代理會建立代理物件並返回,之後將原有A物件從三級快取中刪除,並將A的代理物件加入到二級快取earlySingletonObjects中,之後將A的代理物件注入給B

      • B執行initializeBean方法,呼叫後置處理器及afterProperties方法,這裡提到後置處理器,同樣會判斷B是否被代理,如果被代理則會建立B的代理物件並返回

      • B建立結束之後,會回到getSingleton---2方法,呼叫addSingleton(beanName, singletonObject);方法,如下

        protected void addSingleton(String beanName, Object singletonObject) {
           synchronized (this.singletonObjects) {
              this.singletonObjects.put(beanName, singletonObject);
              this.singletonFactories.remove(beanName);
              this.earlySingletonObjects.remove(beanName);
              this.registeredSingletons.add(beanName);
           }
        }
        

        這裡會將B從三級快取中刪除,並加入到一級快取中

      • 將B建立好的物件注入到A中

      • A執行initializeBean方法,進行初始化,初始化完成

      • 回到getSingleton--2,執行DefaultSingletonBeanRegistry#addSingleton

        protected void addSingleton(String beanName, Object singletonObject) {
           synchronized (this.singletonObjects) {
              this.singletonObjects.put(beanName, singletonObject);
              this.singletonFactories.remove(beanName);
              this.earlySingletonObjects.remove(beanName);
              this.registeredSingletons.add(beanName);
           }
        }
        

        將A從二級快取中刪除,並加入到一級快取中

從上面步驟可以看出

  • 三級快取分別用於存放下面三類物件

    • 一級快取singletonObjects

      完全建立好的物件,如果被代理,則存放代理物件

    • 二級快取earlySingletonObjects

      未完全建立好的代理物件

    • 三級快取singletonFactories

      只進行了例項化,未進行屬性注入和初始化的物件

  • 為何上面機制生效

    由於提前暴露了A物件的引用!,因而在B注入好不完整的A物件後,B以為自己建立好了,這時會注入給A,同時A也會將此B物件當作建立好的,並注入給自己,這樣A就建立完成了,由於B保留著A的引用,這樣B也就真建立完成了

3.4 AoP的考慮

如上在有迴圈依賴的情況下,假設A被代理,那麼需要將A的代理物件注入給B,這時通過getSingleton方法從三級快取獲取物件的過程中,由於ObjectFactory的getObject方法被重寫為AbstractAutowireCapableBeanFactory#getEarlyBeanReference方法,這時會觸發後置處理器的執行,會呼叫AbstractAutoProxyCreator的postProcessAfterInitialization方法,並返回代理物件,之後將代理物件返回用於注入,並放入二級快取,如果A和除了B的其他物件也構成迴圈依賴,之後直接從二級快取中獲取A的代理物件即可

在沒有迴圈依賴的情況下,不會使用到二級快取,如果A被代理,那麼A會在完全建立後,在呼叫後置處理器序列時,會呼叫AbstractAutoProxyCreator的postProcessAfterInitialization方法,並返回代理物件

從上可以看出,

  • Spring的機制是儘量讓代理物件靠後建立,也即在沒有迴圈依賴時在物件完全建立後再建立代理物件
  • 在延遲建立代理物件的機制下,必須有二級快取,這樣在從三級快取中獲取時,會呼叫ObjectFactory方法,其又呼叫getEarlyBeanReference方法完成代理物件建立,之後二級快取用於儲存代理物件,而一級快取用於存放完全建立完成的物件
  • Spring中,如果呼叫某個代理物件a的方法,其中又呼叫了代理物件b的方法,而不是物件b的方法

# 參考

Spring迴圈依賴三級快取是否可以減少為二級快取? - SegmentFault 思否

高頻面試題:Spring 如何解決迴圈依賴? - 知乎 (zhihu.com)

Spring-bean的迴圈依賴以及解決方式_惜暮-CSDN部落格_spring 迴圈依賴

相關文章