Spring循環(huán)依賴的解決
什么是循環(huán)依賴
循環(huán)依賴,是依賴關系形成了一個圓環(huán)。比如:A對象有一個屬性B,那么這時候我們稱之為A依賴B,如果這時候B對象里面有一個屬性A。那么這時候A和B的依賴關系就形成了一個循環(huán),這就是所謂的循環(huán)依賴。如果這時候IOC容器創(chuàng)建A對象的時候,發(fā)現(xiàn)B屬性,然后創(chuàng)建B對象,發(fā)現(xiàn)里面有A屬性,然后創(chuàng)建B.....這么無限循環(huán)下去。我們先用代碼演示一下:
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();
}
}
運行一下結(jié)果
那么我們可以看到循環(huán)依賴存在的問題了
- 棧內(nèi)存溢出
- 程序的維護性和擴展性太差
顯然這種思路是不正確的。
產(chǎn)生循環(huán)依賴產(chǎn)生的條件:
- 在容器中創(chuàng)建的對象是單例的
- 對象是循環(huán)依賴
精簡版解決方案
如果我們自己寫的話,該如何解決的呢?
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();//創(chuàng)建a對象
B b = new B();//因為a對象依賴B,那么創(chuàng)建B
b.setA(a);//創(chuàng)建B對象的時候,發(fā)現(xiàn)依賴A,那么把通過構(gòu)造方法生成的對象a賦值給B
a.setB(b);//然后把生成的b對象注入到a里面
}
}
Spring解決方案
當使用Spring的 @Autowired 注解的時候,其實Spring的實現(xiàn)原理和上面很相似,先通過生成相關的對象,然后再把里面需要依賴的對象設置進去。
我們現(xiàn)在從Spring源碼來走一遍。。
我們現(xiàn)貼出最基本的測試代碼
@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");
}
}
在我們之前發(fā)布的SpringBoot啟動流程源碼分析里面,我們提到過bean單例的生成是在Spring容器創(chuàng)建過程中來完成的。經(jīng)過多層的調(diào)用,最終會調(diào)用到 doGetBean 這個方法里面。
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
...
Object bean;
//先從緩存中獲取是否定義了對應的類,這里的緩存包括了半成品類緩存(只生成了類,但是還沒有進行屬性注入的類)和成品類緩存(已經(jīng)完成了屬性注入的類)
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
......
//如果符合條件,直接從對飲給的bean單例中獲取到對象,然后返回
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
...
try {
.....
//創(chuàng)建單例bean,解決循環(huán)依賴的根本方案
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
//調(diào)用創(chuàng)建單例的方法
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的創(chuàng)建
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) {
//創(chuàng)建beanDefinition所對應的的參數(shù)的bean實例,這里通過構(gòu)造方法或者工廠方法或者cglib創(chuàng)建了對象
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
if (earlySingletonExposure) {
//將對象放到registeredSingletons隊列中,并從earlySingletonObjects中移除
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
...
//注入A的依賴,這里面會發(fā)現(xiàn)屬性,然后從doGetBean()方法開始,生成B對象,然后循環(huán)走到這里的時候,在隊列里面會同時存在A對象和B對象。然后B對象注入A成功,返回后將生成的B注入到A,此時完成了A和B的對象生成,并解決了循環(huán)依賴問題
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
}
加載過程比較長,其實主要是在加載的過程中將對象的創(chuàng)建過程進行了分類處理,在創(chuàng)建的不同時期,放入到隊列來進行區(qū)分。
- singletonObjects:單例對象列表
- singletonFactories:單例工廠隊列,對象剛開始創(chuàng)建的時候,會放入到這個隊列。
- earlySingletonObjects:產(chǎn)生了循環(huán)依賴的對象隊列,對象在創(chuàng)建之后,進行注入過程中,發(fā)現(xiàn)產(chǎn)生了循環(huán)依賴,那么會將對象放入到這個隊列,并且從singletonFactories中移除掉。
- singletonsCurrentlyInCreation:正在創(chuàng)建的對象隊列,整個創(chuàng)建過程都存放在這個隊列里面,當完成了所有的依賴注入以后,從這個隊列里面移除
- registeredSingletons:已經(jīng)創(chuàng)建成功的單例列表。
知道了這幾個隊列以后,我們可以來整理測試例子中,A和B對象是如何一步步創(chuàng)建,并解決其循環(huán)依賴的問題了。
- 首先,依次從singletonObjects,earlySingletonObjects,singletonFactories隊列中去尋找a對象,發(fā)現(xiàn)都沒有,返回了null。那么這時候就需要創(chuàng)建B對象
- a的創(chuàng)建的準備:在創(chuàng)建之前,將a放入到singletonsCurrentlyInCreation隊列,表明a正在進行創(chuàng)建。
- 開始創(chuàng)建a:通過反射創(chuàng)建對象a。
- 進行創(chuàng)建后的處理:創(chuàng)建a對象以后,將a放入到singletonFactories和registeredSingletons隊列,并從earlySingletonObjects中移除。然后進行依賴注入工作,發(fā)現(xiàn)有依賴B對象。
- 這時候進入了B對象的注入過程
- 首先,依次從singletonObjects,earlySingletonObjects,singletonFactories隊列中去尋找b對象,發(fā)現(xiàn)都沒有,返回了null。那么這時候就需要創(chuàng)建B對象
- b的創(chuàng)建的準備工作:在創(chuàng)建之前,將b放入到singletonsCurrentlyInCreation隊列,表明b正在進行創(chuàng)建
- 開始創(chuàng)建b:通過反射創(chuàng)建對象b。
- 進行創(chuàng)建后的處理:將b放入到singletonFactories和registeredSingletons隊列,并從earlySingletonObjects中移除。然后進行依賴注入工作,發(fā)現(xiàn)有依賴 A對象。
- 這時候進入A的注入過程。。。
- 從singletonObjects中查找a,發(fā)現(xiàn)a不存在但是singletonsCurrentlyInCreation隊列中有a,那么這時候說明a是在創(chuàng)建過程中的,此處又需要創(chuàng)建,屬于循環(huán)依賴了。然后去earlySingletonObjects查找,也沒發(fā)現(xiàn)。那么這時候去singletonFactories隊列中去尋找a對象,找到了。這時候?qū)對象放入到earlySingletonObjects隊列,并從singletonFactories中移除。因為發(fā)現(xiàn)了a對象,這里直接返回a,此時完成了b對象對A的依賴注入了
- b實例化完成,而且依賴也注入完成了,那么進行最后的處理。將b實例從singletonsCurrentlyInCreation隊列移除,表明b對象實例化結(jié)束。然后將b放入到singletonObjects和registeredSingletons隊列,并從singletonFactories和earlySingletonObjects隊列移除。最后將b對象注入到a對象中。然后a完成了創(chuàng)建過程。
- a實例化完成,而且依賴也注入完成了,那么進行最后的處理。將a實例從singletonsCurrentlyInCreation隊列移除,表明a對象實例化結(jié)束。然后將a放入到singletonObjects和registeredSingletons隊列,并從singletonFactories和earlySingletonObjects隊列移除。此時完成了a對象的創(chuàng)建。
總結(jié)
上述就是spring解決循環(huán)依賴的整體過程,跟我們之前的那個方法很相似,只是對于各種情況的處理更仔細。而且從這個過程也能理解spring對于對象的創(chuàng)建過程。
本文由 開了肯 發(fā)布!