Spring迴圈依賴的解決
阿新 • • 發佈:2020-01-14
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(); } }
執行一下結果
那麼我們可以看到迴圈依賴存在的問題了
- 棧記憶體溢位
- 程式的維護性和擴充套件性太差
顯然這種思路是不正確的。
產生迴圈依賴產生的條件:
- 在容器中建立的物件是單例的
- 物件是迴圈依賴
精簡版解決方案
如果我們自己寫的話,該如何解決的呢?
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);
}
...
}
載入過程比較長,其實主要是在載入的過程中將物件的建立過程進行了分類處理,在建立的不同時期,放入到佇列來進行區分。
- singletonObjects:單例物件列表
- singletonFactories:單例工廠佇列,物件剛開始建立的時候,會放入到這個佇列。
- earlySingletonObjects:產生了迴圈依賴的物件佇列,物件在建立之後,進行注入過程中,發現產生了迴圈依賴,那麼會將物件放入到這個佇列,並且從singletonFactories中移除掉。
- singletonsCurrentlyInCreation:正在建立的物件佇列,整個建立過程都存放在這個佇列裡面,當完成了所有的依賴注入以後,從這個佇列裡面移除
- registeredSingletons:已經建立成功的單例列表。
知道了這幾個佇列以後,我們可以來整理測試例子中,A和B物件是如何一步步建立,並解決其迴圈依賴的問題了。
- 首先,依次從singletonObjects,earlySingletonObjects,singletonFactories佇列中去尋找a物件,發現都沒有,返回了null。那麼這時候就需要建立B物件
- a的建立的準備:在建立之前,將a放入到singletonsCurrentlyInCreation佇列,表明a正在進行建立。
- 開始建立a:通過反射建立物件a。
- 進行建立後的處理:建立a物件以後,將a放入到singletonFactories和registeredSingletons佇列,並從earlySingletonObjects中移除。然後進行依賴注入工作,發現有依賴B物件。
- 這時候進入了B物件的注入過程
- 首先,依次從singletonObjects,earlySingletonObjects,singletonFactories佇列中去尋找b物件,發現都沒有,返回了null。那麼這時候就需要建立B物件
- b的建立的準備工作:在建立之前,將b放入到singletonsCurrentlyInCreation佇列,表明b正在進行建立
- 開始建立b:通過反射建立物件b。
- 進行建立後的處理:將b放入到singletonFactories和registeredSingletons佇列,並從earlySingletonObjects中移除。然後進行依賴注入工作,發現有依賴 A物件。
- 這時候進入A的注入過程。。。
- 從singletonObjects中查詢a,發現a不存在但是singletonsCurrentlyInCreation佇列中有a,那麼這時候說明a是在建立過程中的,此處又需要建立,屬於迴圈依賴了。然後去earlySingletonObjects查詢,也沒發現。那麼這時候去singletonFactories佇列中去尋找a物件,找到了。這時候將a物件放入到earlySingletonObjects佇列,並從singletonFactories中移除。因為發現了a物件,這裡直接返回a,此時完成了b物件對A的依賴注入了
- b例項化完成,而且依賴也注入完成了,那麼進行最後的處理。將b例項從singletonsCurrentlyInCreation佇列移除,表明b物件例項化結束。然後將b放入到singletonObjects和registeredSingletons佇列,並從singletonFactories和earlySingletonObjects佇列移除。最後將b物件注入到a物件中。然後a完成了建立過程。
- a例項化完成,而且依賴也注入完成了,那麼進行最後的處理。將a例項從singletonsCurrentlyInCreation佇列移除,表明a物件例項化結束。然後將a放入到singletonObjects和registeredSingletons佇列,並從singletonFactories和earlySingletonObjects佇列移除。此時完成了a物件的建立。
總結
上述就是spring解決迴圈依賴的整體過程,跟我們之前的那個方法很相似,只是對於各種情況的處理更仔細。而且從這個過程也能理解spring對於物件的建立過程。
本文由 開了肯 釋出!