Spring循環(huán)依賴(lài)的解決
什么是循環(huán)依賴(lài)
循環(huán)依賴(lài),是依賴(lài)關(guān)系形成了一個(gè)圓環(huán)。比如:A對(duì)象有一個(gè)屬性B埋心,那么這時(shí)候我們稱(chēng)之為A依賴(lài)B,如果這時(shí)候B對(duì)象里面有一個(gè)屬性A忙上。那么這時(shí)候A和B的依賴(lài)關(guān)系就形成了一個(gè)循環(huán)拷呆,這就是所謂的循環(huán)依賴(lài)。如果這時(shí)候IOC容器創(chuàng)建A對(duì)象的時(shí)候疫粥,發(fā)現(xiàn)B屬性茬斧,然后創(chuàng)建B對(duì)象,發(fā)現(xiàn)里面有A屬性梗逮,然后創(chuàng)建B.....這么無(wú)限循環(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();
}
}
運(yùn)行一下結(jié)果
那么我們可以看到循環(huán)依賴(lài)存在的問(wèn)題了
- 棧內(nèi)存溢出
- 程序的維護(hù)性和擴(kuò)展性太差
顯然這種思路是不正確的蛹屿。
產(chǎn)生循環(huán)依賴(lài)產(chǎn)生的條件:
- 在容器中創(chuàng)建的對(duì)象是單例的
- 對(duì)象是循環(huán)依賴(lài)
精簡(jiǎn)版解決方案
如果我們自己寫(xiě)的話(huà)微峰,該如何解決的呢剥纷?
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對(duì)象
B b = new B();//因?yàn)閍對(duì)象依賴(lài)B涮毫,那么創(chuàng)建B
b.setA(a);//創(chuàng)建B對(duì)象的時(shí)候,發(fā)現(xiàn)依賴(lài)A贷屎,那么把通過(guò)構(gòu)造方法生成的對(duì)象a賦值給B
a.setB(b);//然后把生成的b對(duì)象注入到a里面
}
}
Spring解決方案
當(dāng)使用Spring的 @Autowired 注解的時(shí)候罢防,其實(shí)Spring的實(shí)現(xiàn)原理和上面很相似,先通過(guò)生成相關(guān)的對(duì)象唉侄,然后再把里面需要依賴(lài)的對(duì)象設(shè)置進(jìn)去咒吐。
我們現(xiàn)在從Spring源碼來(lái)走一遍。属划。
我們現(xiàn)貼出最基本的測(cè)試代碼
@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得時(shí)候才進(jìn)行IOC容器中的對(duì)象的實(shí)例化工作
A a = (A) context.getBean("a");
}
}
在我們之前發(fā)布的SpringBoot啟動(dòng)流程源碼分析里面恬叹,我們提到過(guò)bean單例的生成是在Spring容器創(chuàng)建過(guò)程中來(lái)完成的。經(jīng)過(guò)多層的調(diào)用同眯,最終會(huì)調(diào)用到 doGetBean 這個(gè)方法里面绽昼。
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
...
Object bean;
//先從緩存中獲取是否定義了對(duì)應(yīng)的類(lèi),這里的緩存包括了半成品類(lèi)緩存(只生成了類(lèi)须蜗,但是還沒(méi)有進(jìn)行屬性注入的類(lèi))和成品類(lèi)緩存(已經(jīng)完成了屬性注入的類(lèi))
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
......
//如果符合條件硅确,直接從對(duì)飲給的bean單例中獲取到對(duì)象,然后返回
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
...
try {
.....
//創(chuàng)建單例bean明肮,解決循環(huán)依賴(lài)的根本方案
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 {
...
//進(jìn)行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的包裝類(lèi)
BeanWrapper instanceWrapper = null;
...
if (instanceWrapper == null) {
//創(chuàng)建beanDefinition所對(duì)應(yīng)的的參數(shù)的bean實(shí)例菱农,這里通過(guò)構(gòu)造方法或者工廠方法或者cglib創(chuàng)建了對(duì)象
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
if (earlySingletonExposure) {
//將對(duì)象放到registeredSingletons隊(duì)列中,并從earlySingletonObjects中移除
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
...
//注入A的依賴(lài)柿估,這里面會(huì)發(fā)現(xiàn)屬性循未,然后從doGetBean()方法開(kāi)始,生成B對(duì)象秫舌,然后循環(huán)走到這里的時(shí)候的妖,在隊(duì)列里面會(huì)同時(shí)存在A對(duì)象和B對(duì)象。然后B對(duì)象注入A成功足陨,返回后將生成的B注入到A羔味,此時(shí)完成了A和B的對(duì)象生成,并解決了循環(huán)依賴(lài)問(wèn)題
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
...
}
加載過(guò)程比較長(zhǎng)钠右,其實(shí)主要是在加載的過(guò)程中將對(duì)象的創(chuàng)建過(guò)程進(jìn)行了分類(lèi)處理,在創(chuàng)建的不同時(shí)期忘蟹,放入到隊(duì)列來(lái)進(jìn)行區(qū)分飒房。
- singletonObjects:單例對(duì)象列表
- singletonFactories:單例工廠隊(duì)列,對(duì)象剛開(kāi)始創(chuàng)建的時(shí)候媚值,會(huì)放入到這個(gè)隊(duì)列狠毯。
- earlySingletonObjects:產(chǎn)生了循環(huán)依賴(lài)的對(duì)象隊(duì)列,對(duì)象在創(chuàng)建之后褥芒,進(jìn)行注入過(guò)程中嚼松,發(fā)現(xiàn)產(chǎn)生了循環(huán)依賴(lài)嫡良,那么會(huì)將對(duì)象放入到這個(gè)隊(duì)列,并且從singletonFactories中移除掉献酗。
- singletonsCurrentlyInCreation:正在創(chuàng)建的對(duì)象隊(duì)列寝受,整個(gè)創(chuàng)建過(guò)程都存放在這個(gè)隊(duì)列里面,當(dāng)完成了所有的依賴(lài)注入以后罕偎,從這個(gè)隊(duì)列里面移除
- registeredSingletons:已經(jīng)創(chuàng)建成功的單例列表很澄。
知道了這幾個(gè)隊(duì)列以后,我們可以來(lái)整理測(cè)試?yán)又醒占埃珹和B對(duì)象是如何一步步創(chuàng)建甩苛,并解決其循環(huán)依賴(lài)的問(wèn)題了。
- 首先俏站,依次從singletonObjects讯蒲,earlySingletonObjects,singletonFactories隊(duì)列中去尋找a對(duì)象肄扎,發(fā)現(xiàn)都沒(méi)有墨林,返回了null。那么這時(shí)候就需要?jiǎng)?chuàng)建B對(duì)象
- a的創(chuàng)建的準(zhǔn)備:在創(chuàng)建之前反浓,將a放入到singletonsCurrentlyInCreation隊(duì)列萌丈,表明a正在進(jìn)行創(chuàng)建。
- 開(kāi)始創(chuàng)建a:通過(guò)反射創(chuàng)建對(duì)象a雷则。
- 進(jìn)行創(chuàng)建后的處理:創(chuàng)建a對(duì)象以后辆雾,將a放入到singletonFactories和registeredSingletons隊(duì)列,并從earlySingletonObjects中移除。然后進(jìn)行依賴(lài)注入工作月劈,發(fā)現(xiàn)有依賴(lài)B對(duì)象度迂。
- 這時(shí)候進(jìn)入了B對(duì)象的注入過(guò)程
- 首先,依次從singletonObjects猜揪,earlySingletonObjects惭墓,singletonFactories隊(duì)列中去尋找b對(duì)象,發(fā)現(xiàn)都沒(méi)有而姐,返回了null腊凶。那么這時(shí)候就需要?jiǎng)?chuàng)建B對(duì)象
- b的創(chuàng)建的準(zhǔn)備工作:在創(chuàng)建之前,將b放入到singletonsCurrentlyInCreation隊(duì)列拴念,表明b正在進(jìn)行創(chuàng)建
- 開(kāi)始創(chuàng)建b:通過(guò)反射創(chuàng)建對(duì)象b钧萍。
- 進(jìn)行創(chuàng)建后的處理:將b放入到singletonFactories和registeredSingletons隊(duì)列,并從earlySingletonObjects中移除。然后進(jìn)行依賴(lài)注入工作政鼠,發(fā)現(xiàn)有依賴(lài) A對(duì)象风瘦。
- 這時(shí)候進(jìn)入A的注入過(guò)程。公般。万搔。
- 從singletonObjects中查找a胡桨,發(fā)現(xiàn)a不存在但是singletonsCurrentlyInCreation隊(duì)列中有a,那么這時(shí)候說(shuō)明a是在創(chuàng)建過(guò)程中的瞬雹,此處又需要?jiǎng)?chuàng)建昧谊,屬于循環(huán)依賴(lài)了。然后去earlySingletonObjects查找挖炬,也沒(méi)發(fā)現(xiàn)揽浙。那么這時(shí)候去singletonFactories隊(duì)列中去尋找a對(duì)象,找到了意敛。這時(shí)候?qū)對(duì)象放入到earlySingletonObjects隊(duì)列馅巷,并從singletonFactories中移除。因?yàn)榘l(fā)現(xiàn)了a對(duì)象草姻,這里直接返回a钓猬,此時(shí)完成了b對(duì)象對(duì)A的依賴(lài)注入了
- b實(shí)例化完成,而且依賴(lài)也注入完成了撩独,那么進(jìn)行最后的處理敞曹。將b實(shí)例從singletonsCurrentlyInCreation隊(duì)列移除,表明b對(duì)象實(shí)例化結(jié)束综膀。然后將b放入到singletonObjects和registeredSingletons隊(duì)列澳迫,并從singletonFactories和earlySingletonObjects隊(duì)列移除。最后將b對(duì)象注入到a對(duì)象中剧劝。然后a完成了創(chuàng)建過(guò)程橄登。
- a實(shí)例化完成,而且依賴(lài)也注入完成了讥此,那么進(jìn)行最后的處理拢锹。將a實(shí)例從singletonsCurrentlyInCreation隊(duì)列移除,表明a對(duì)象實(shí)例化結(jié)束萄喳。然后將a放入到singletonObjects和registeredSingletons隊(duì)列卒稳,并從singletonFactories和earlySingletonObjects隊(duì)列移除。此時(shí)完成了a對(duì)象的創(chuàng)建他巨。
總結(jié)
上述就是spring解決循環(huán)依賴(lài)的整體過(guò)程充坑,跟我們之前的那個(gè)方法很相似,只是對(duì)于各種情況的處理更仔細(xì)染突。而且從這個(gè)過(guò)程也能理解spring對(duì)于對(duì)象的創(chuàng)建過(guò)程匪傍。
本文由 開(kāi)了肯 發(fā)布!