1. 程式人生 > >Spring迴圈依賴的解決

Spring迴圈依賴的解決

Spring迴圈依賴的解決

什麼是迴圈依賴

迴圈依賴,是依賴關係形成了一個圓環。比如:A物件有一個屬性B,那麼這時候我們稱之為A依賴B,如果這時候B物件裡面有一個屬性A。那麼這時候A和B的依賴關係就形成了一個迴圈,這就是所謂的迴圈依賴。如果這時候IOC容器建立A物件的時候,發現B屬性,然後建立B物件,發現裡面有A屬性,然後建立B.....這麼無限迴圈下去。我們先用程式碼演示一下:

public class A {
    private B b=new B();
}

public class B {
    private A a=new A();
}

public class Test {
    public static void main(String[] args) {
        A a = new A();
    }
}

執行一下結果

那麼我們可以看到迴圈依賴存在的問題了

  1. 棧記憶體溢位
  2. 程式的維護性和擴充套件性太差

顯然這種思路是不正確的。

產生迴圈依賴產生的條件:

  1. 在容器中建立的物件是單例的
  2. 物件是迴圈依賴

精簡版解決方案

如果我們自己寫的話,該如何解決的呢?

public class A {
    private B b;
    public void setB(B b) {
        this.b = b;
    }
}
public class B {
    private A a;
    public void setA(A a) {
        this.a = a;
    }
}
public class Test {
    public static void main(String[] args) {
        A a = new A();//建立a物件
        B b = new B();//因為a物件依賴B,那麼建立B
        b.setA(a);//建立B物件的時候,發現依賴A,那麼把通過構造方法生成的物件a賦值給B
         a.setB(b);//然後把生成的b物件注入到a裡面
    }
}

Spring解決方案

當使用Spring的 @Autowired 註解的時候,其實Spring的實現原理和上面很相似,先通過生成相關的物件,然後再把裡面需要依賴的物件設定進去。

我們現在從Spring原始碼來走一遍。。

我們現貼出最基本的測試程式碼

@Component
public class A {
   @Autowired
   B b;
}
@Component
public class B {
    @Autowired
    A a;
}
public class RecyclerTest {
    @Test
    public void test() {
        ApplicationContext context = new AnnotationConfigApplicationContext("com.kailaisi.demo.recycler");
        //getbean得時候才進行IOC容器中的物件的例項化工作
        A a = (A) context.getBean("a");
    }
}

在我們之前釋出的SpringBoot啟動流程原始碼分析裡面,我們提到過bean單例的生成是在Spring容器建立過程中來完成的。經過多層的呼叫,最終會呼叫到 doGetBean 這個方法裡面。

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
      @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
   ...
   Object bean;
   //先從快取中獲取是否定義了對應的類,這裡的快取包括了半成品類快取(只生成了類,但是還沒有進行屬性注入的類)和成品類快取(已經完成了屬性注入的類)
   Object sharedInstance = getSingleton(beanName);
   if (sharedInstance != null && args == null) {
      ......
      //如果符合條件,直接從對飲給的bean單例中獲取到物件,然後返回
      bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
   }
   else {
      ...
      try {
         .....
         //建立單例bean,解決迴圈依賴的根本方案
         if (mbd.isSingleton()) {
            sharedInstance = getSingleton(beanName, () -> {
               try {
                  //呼叫建立單例的方法
                  return createBean(beanName, mbd, args);
               }
               ...
            });
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
         }
   ...
   return (T) bean;
}

    @Override
    protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
            throws BeanCreationException {
            ...
            //進行bean的建立
            Object beanInstance = doCreateBean(beanName, mbdToUse, args);
            ...
    }
    
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
            throws BeanCreationException {
        //bean的包裝類
        BeanWrapper instanceWrapper = null;
        ...
        if (instanceWrapper == null) {
            //建立beanDefinition所對應的的引數的bean例項,這裡通過構造方法或者工廠方法或者cglib建立了物件
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        if (earlySingletonExposure) {
            //將物件放到registeredSingletons佇列中,並從earlySingletonObjects中移除
            addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
        ...
            //注入A的依賴,這裡面會發現屬性,然後從doGetBean()方法開始,生成B物件,然後迴圈走到這裡的時候,在佇列裡面會同時存在A物件和B物件。然後B物件注入A成功,返回後將生成的B注入到A,此時完成了A和B的物件生成,並解決了迴圈依賴問題
            populateBean(beanName, mbd, instanceWrapper);
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        ...
}

載入過程比較長,其實主要是在載入的過程中將物件的建立過程進行了分類處理,在建立的不同時期,放入到佇列來進行區分。

  1. singletonObjects:單例物件列表
  2. singletonFactories:單例工廠佇列,物件剛開始建立的時候,會放入到這個佇列。
  3. earlySingletonObjects:產生了迴圈依賴的物件佇列,物件在建立之後,進行注入過程中,發現產生了迴圈依賴,那麼會將物件放入到這個佇列,並且從singletonFactories中移除掉。
  4. singletonsCurrentlyInCreation:正在建立的物件佇列,整個建立過程都存放在這個佇列裡面,當完成了所有的依賴注入以後,從這個佇列裡面移除
  5. registeredSingletons:已經建立成功的單例列表。

知道了這幾個佇列以後,我們可以來整理測試例子中,A和B物件是如何一步步建立,並解決其迴圈依賴的問題了。

  1. 首先,依次從singletonObjects,earlySingletonObjects,singletonFactories佇列中去尋找a物件,發現都沒有,返回了null。那麼這時候就需要建立B物件
  2. a的建立的準備:在建立之前,將a放入到singletonsCurrentlyInCreation佇列,表明a正在進行建立。
  3. 開始建立a:通過反射建立物件a。
  4. 進行建立後的處理:建立a物件以後,將a放入到singletonFactories和registeredSingletons佇列,並從earlySingletonObjects中移除。然後進行依賴注入工作,發現有依賴B物件。
    1. 這時候進入了B物件的注入過程
    2. 首先,依次從singletonObjects,earlySingletonObjects,singletonFactories佇列中去尋找b物件,發現都沒有,返回了null。那麼這時候就需要建立B物件
    3. b的建立的準備工作:在建立之前,將b放入到singletonsCurrentlyInCreation佇列,表明b正在進行建立
    4. 開始建立b:通過反射建立物件b。
    5. 進行建立後的處理:將b放入到singletonFactories和registeredSingletons佇列,並從earlySingletonObjects中移除。然後進行依賴注入工作,發現有依賴 A物件。
      1. 這時候進入A的注入過程。。。
      2. 從singletonObjects中查詢a,發現a不存在但是singletonsCurrentlyInCreation佇列中有a,那麼這時候說明a是在建立過程中的,此處又需要建立,屬於迴圈依賴了。然後去earlySingletonObjects查詢,也沒發現。那麼這時候去singletonFactories佇列中去尋找a物件,找到了。這時候將a物件放入到earlySingletonObjects佇列,並從singletonFactories中移除。因為發現了a物件,這裡直接返回a,此時完成了b物件對A的依賴注入了
    6. b例項化完成,而且依賴也注入完成了,那麼進行最後的處理。將b例項從singletonsCurrentlyInCreation佇列移除,表明b物件例項化結束。然後將b放入到singletonObjects和registeredSingletons佇列,並從singletonFactories和earlySingletonObjects佇列移除。最後將b物件注入到a物件中。然後a完成了建立過程。
  5. a例項化完成,而且依賴也注入完成了,那麼進行最後的處理。將a例項從singletonsCurrentlyInCreation佇列移除,表明a物件例項化結束。然後將a放入到singletonObjects和registeredSingletons佇列,並從singletonFactories和earlySingletonObjects佇列移除。此時完成了a物件的建立。

總結

上述就是spring解決迴圈依賴的整體過程,跟我們之前的那個方法很相似,只是對於各種情況的處理更仔細。而且從這個過程也能理解spring對於物件的建立過程。

本文由 開了肯 釋出!