1. 什么是循環(huán)依賴
循環(huán)依賴其實就是循環(huán)引用碉怔,也就是兩個或兩個以上的Bean互相持有對方鳞骤,最終形成閉環(huán)址儒。比如A依賴B,B依賴C允悦,C又依賴A。
注意這里不是函數(shù)的循環(huán)調(diào)用虑啤,是對象的相互依賴關(guān)系隙弛。循環(huán)調(diào)用其實就是一個死循環(huán),除非有終結(jié)條件狞山。
Spring中循環(huán)依賴的場景有:
- 構(gòu)造器的循環(huán)依賴(構(gòu)造器注入)
- Field屬性的循環(huán)依賴(set注入)
其中全闷,構(gòu)造器的循環(huán)依賴問題無法解決,只能拋出BeanCurrentlyInCreationException異常萍启,在解決屬性循環(huán)依賴時总珠,Spring采用的是提前暴露對象的方法。
構(gòu)造器注入代碼如下
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public CircularDependencyA(CircularDependencyB circB) {
this.circB = circB;
}
}
@Component
public class CircularDependencyB {
private CircularDependencyA circA;
@Autowired
public CircularDependencyB(CircularDependencyA circA) {
this.circA = circA;
}
}
屬性注入代碼如下
@Component
public class CircularDependencyA {
private CircularDependencyB circB;
@Autowired
public void setCircB(CircularDependencyB circB) {
this.circB = circB;
}
public CircularDependencyB getCircB() {
return circB;
}
}
2. 循環(huán)依賴處理機制
- 單例bean構(gòu)造器參數(shù)循環(huán)注入(無法解決)
- prototype原型bean循環(huán)依賴(無法解決)
對于原型bean的初始化無論是通過構(gòu)造器還是setXxx方法產(chǎn)生循環(huán)依賴時勘纯,Spring都會直接報錯處理局服。
AbstractBeanFactory.doGetBean()?法:
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
在獲取bean之前如果這個原型bean正在被創(chuàng)建則直接拋出異常。原型bean在創(chuàng)建之前會進行標記這個beanName正在被創(chuàng)建驳遵,等創(chuàng)建結(jié)束之后會刪除標記淫奔。
AbstractBeanFactory#doGetBean
try {
//創(chuàng)建原型bean之前添加標記
beforePrototypeCreation(beanName);
//創(chuàng)建原型bean
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
//創(chuàng)建原型bean之后刪除標記
afterPrototypeCreation(beanName);
}
總結(jié):Spring不支持原型bean的循環(huán)依賴。(舉個例子堤结,TestA唆迁,TestB兩個原型Bean相互依賴,當TestA被實例化竞穷,在屬性賦值的時候發(fā)現(xiàn)對象TestB需要實例化唐责,所以就去實例化TestB,到了TestB屬性賦值的時候又發(fā)現(xiàn)依賴TestA就開始創(chuàng)建TestA瘾带,然而TestA被標記正在創(chuàng)建中鼠哥,然后就被isPrototypeCurrentlyInCreation這個判斷攔截住了拋出異常)
- 單例Bean通過setXxx或者@Autowired進行循環(huán)依賴
Spring的循環(huán)依賴的利率一句基于Java的引用傳遞,當獲得對象引用時看政,對象的屬性是可以延后設(shè)置的肴盏,但是構(gòu)造器必須在獲取引用之前。
Spring通過setXxx或者@Autowired方法解決循環(huán)依賴其實是通過提前暴露一個ObjectFactory對象來完成的帽衙,簡單來說ClassA在調(diào)用構(gòu)造器完成對象初始化之后菜皂,在調(diào)用ClassA的setClassB方法之前就把ClassA實例化的對象通過ObjectFactory提前暴露給Spring容器中。
- Spring容器初始化ClassA通過構(gòu)造器初始化對象提前暴露給Spring容器厉萝。
AbstractAutowireCapableBeanFactory#doCreateBean
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");
}
//將初始化后的對象提前已ObjectFactory對象注入到容器中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
- ClassA調(diào)用setClassB方法恍飘,Spring首先從容器中獲取ClassB榨崩,此時ClassB不存在Spring容器中
- Spring容器初始化ClassB,同時也將ClassB提前暴露給Spring容器
- ClassB調(diào)用setClassA方法章母,Spring從容器中獲取ClassA母蛛,因為第一步已經(jīng)提前暴露了ClassA,因此可以獲取到ClassA實例乳怎。
ClassA通過spring容器獲取到ClassB彩郊,完成對象初始化操作
5.這樣ClassA和ClassB都完成了對象初始化操作,解決了循環(huán)依賴的問題蚪缀。