- 什么是循環(huán)依賴?
先看代碼:
public class A {
private B b;
// 省略set/get方法
}
public class B {
private A a;
// 省略set/get方法
}
可以看到A類里有一個(gè)屬性是B類對(duì)象迎变,而B類里也有一個(gè)屬性是A類對(duì)象充尉,則我們可以稱A類對(duì)象與B類對(duì)象之間互相循環(huán)依賴。然后我們對(duì)把這倆個(gè)類納入到IOC容器中進(jìn)行管理衣形,現(xiàn)在進(jìn)行xml配置:
<bean id="a" class="com.A">
<property name="b" ref="b"/>
</bean>
<bean id="b" class="com.B">
<property name="a" ref="a"/>
</bean>
當(dāng)配置好xml以后驼侠,我們創(chuàng)建容器,并且調(diào)用getBean方法來獲取某個(gè)對(duì)象泵喘,那么會(huì)發(fā)生什么事情呢泪电?正常邏輯應(yīng)該是發(fā)生了死循環(huán),a對(duì)象的創(chuàng)建需要依賴b對(duì)象纪铺,而b對(duì)象的創(chuàng)建同時(shí)也需要a對(duì)象相速。這簡直就是沒辦法解決嘛!但是SpringIOC卻解決了這個(gè)問題鲜锚,并且你可以正常的獲取到相應(yīng)的對(duì)象而不會(huì)發(fā)生錯(cuò)誤突诬。
那么SpringIOC是如何解決循環(huán)依賴的問題呢?
原理
SpringIOC解決循環(huán)依賴的思路就是依靠緩存芜繁,同時(shí)還得引出個(gè)概念即早期暴露引用旺隙。我們知道在IOC容器里bean的初始化的過程分為三個(gè)步驟:創(chuàng)建實(shí)例、屬性注入實(shí)例骏令、回調(diào)實(shí)例實(shí)現(xiàn)的接口方法蔬捷。解決思路就在這:當(dāng)我們創(chuàng)建實(shí)例與屬性注入實(shí)例這倆個(gè)步驟之間的時(shí)候,我們引入緩存榔袋,將這些已經(jīng)創(chuàng)建好但是并沒有注入屬性的實(shí)例放到緩存里周拐,而這些放在緩存里但是沒有被注入屬性的實(shí)例對(duì)象,就是解決循環(huán)依賴的方法凰兑,打個(gè)比方:A對(duì)象的創(chuàng)建需要引用到B對(duì)象妥粟,而B對(duì)象的創(chuàng)建也需要A對(duì)象,而此時(shí)當(dāng)B對(duì)象創(chuàng)建的時(shí)候直接從緩存里引用A對(duì)象(雖然不是完全體A對(duì)象吏够,畢竟沒有賦值處理)勾给,當(dāng)B對(duì)象完成創(chuàng)建以后再被A對(duì)象引用進(jìn)去,則A對(duì)象也完成了創(chuàng)建锅知。
這就是SpringIOC解決bean直接循環(huán)依賴的思路當(dāng)然有一個(gè)小問題播急,IOC能夠解決的只能是屬性之間的循環(huán)依賴,如果有bean之間的構(gòu)造器相互依賴則就解決不了只能報(bào)錯(cuò)了喉镰。
- 我們現(xiàn)在來看看Spring IOC的源碼
先看一下下面介紹源碼里的緩存的表:
源碼 | 級(jí)別 | 描述 |
---|---|---|
singletonObjects | 一級(jí)緩存 | 用于存放完全初始化好的 bean旅择,從該緩存中取出的 bean 可以直接使用 |
earlySingletonObjects | 二級(jí)緩存 | 存放原始的 bean 對(duì)象(尚未填充屬性),用于解決循環(huán)依賴 |
singletonFactories | 三級(jí)緩存 | 存放 bean 工廠對(duì)象侣姆,用于解決循環(huán)依賴 |
省略不必要的代碼
protected <T> T doGetBean( final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
Object bean;
// 從緩存中取得bean的實(shí)例
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
//進(jìn)行后續(xù)處理生真,如果是正常的普通bean則返回普通的bean,如果是實(shí)現(xiàn)了FactoryBean接口的bean則返回的是getObject里的內(nèi)容
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// 解決依賴的問題捺宗,這個(gè)跟我們說的依賴是不一樣的.可以忽略
// ......
// 創(chuàng)建單例 bean
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// 發(fā)生異常柱蟀,銷毀bean
destroySingleton(beanName);
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// Check if required type matches the type of the actual bean instance.
// ......
return (T) bean;
}
以上是doGetBean方法里的代碼,當(dāng)然我省略了跟本章無關(guān)的代碼蚜厉。
一步步來吧长已,先進(jìn)行初始化a對(duì)象的操作,然后發(fā)現(xiàn)調(diào)用的是createBean(String beanName, RootBeanDefinition mbd, Object[] args)方法昼牛,而真正起作用的是doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)方法术瓮。而在這個(gè)方法里面包含了三個(gè)重要的方法createBeanInstance、populateBean贰健、initializeBean胞四,看過之前系列文章的人都知道這三個(gè)方法分別代表:創(chuàng)建實(shí)例、屬性注入伶椿、方法回調(diào)辜伟,這是bean初始化的核心方法。當(dāng)然下面這段代碼是在createBeanInstance和populateBean中間的一段doCreateBean的代碼脊另。
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// ......
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
這段代碼在spring源碼注釋里描述是用來解決循環(huán)依賴的問題的导狡。包含了一個(gè)匿名內(nèi)部類ObjectFactory<T>(普通的工廠類返回的是getObject方法返回的對(duì)象),用getEarlyBeanReference實(shí)現(xiàn)了getObject方法偎痛。同時(shí)還調(diào)用了addSingletonFactory方法旱捧。分別來看一下各自方法的實(shí)現(xiàn):
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
if (exposedObject == null) {
return null;
}
}
}
}
return exposedObject;
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) {
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
可以看到在addSingletonFactory方法中,會(huì)將beanName與singletonFactory形成kv關(guān)系put進(jìn)singletonFactories里面踩麦。并且將earlySingletonObjects里面的key值為beanName的kv進(jìn)行移除枚赡。
此時(shí)a對(duì)象的早期暴露引用已經(jīng)存在了singletonFactories三級(jí)緩存里面。此時(shí)a對(duì)象進(jìn)行populateBean方法進(jìn)行屬性注入靖榕,發(fā)現(xiàn)需要依賴b對(duì)象标锄,緊接著就是去初始化b對(duì)象。繼續(xù)重復(fù)上面的步驟到b對(duì)象進(jìn)行屬性注入這一步的時(shí)候(此時(shí)singletonFactories三級(jí)緩存里已經(jīng)有了a對(duì)象的提前暴露引用和b對(duì)象的提前暴露引用的工廠對(duì)象)茁计,發(fā)現(xiàn)需要依賴a對(duì)象料皇,此時(shí)去獲取a對(duì)象,看代碼:
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
//繼續(xù)看這個(gè)方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
先從singletonObjects一級(jí)緩存里取星压,如果沒有取到践剂,則從earlySingletonObjects二級(jí)緩存里取,如果還是沒取到娜膘,則從singletonFactories三級(jí)緩存里取逊脯,取到以后進(jìn)行g(shù)etObject方法返回早期暴露對(duì)象引用,同時(shí)放進(jìn)earlySingletonObjects二級(jí)緩存里竣贪,并且三級(jí)緩存里進(jìn)行刪除該kv军洼。
那么到此巩螃,a對(duì)象的早期暴露引用已經(jīng)被b對(duì)象獲取到了,并且在singletonFactories三級(jí)緩存里已經(jīng)沒有a對(duì)象的早期暴露引用的工廠對(duì)象了匕争,a對(duì)象的早期暴露引用存在了二級(jí)緩存earlySingletonObjects里面避乏,當(dāng)然singletonFactories三級(jí)緩存依然有b對(duì)象的早期暴露引用的工廠對(duì)象。
繼續(xù):b對(duì)象拿到了a對(duì)象的早期暴露引用甘桑,進(jìn)行完屬性注入以后拍皮,則返回一個(gè)b對(duì)象了同時(shí)調(diào)用方法getSingleton(String beanName, ObjectFactory<?> singletonFactory),看源碼:
//我已經(jīng)刪除了很多無關(guān)的代碼
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
// ......
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// ......
beforeSingletonCreation(beanName);
boolean newSingleton = false;
// ... ...
//... ...
try {
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
finally {
// ... ...
}
if (newSingleton) {
addSingleton(beanName, singletonObject);
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
其實(shí)就是倆個(gè)方法:singletonObject = singletonFactory.getObject();和addSingleton(beanName, singletonObject);至此我們不需要說明第一個(gè)了跑杭,著重來看一下addSingleton方法铆帽。
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
ok,上面源碼已經(jīng)說明了此時(shí)singletonObjects一級(jí)緩存將要存入b對(duì)象德谅,而二級(jí)緩存earlySingletonObjects和三級(jí)緩存singletonFactories則把相關(guān)緩存的對(duì)象移除爹橱。至此b對(duì)象則只存在一級(jí)緩存singletonObjects里面了。
當(dāng)b對(duì)象完成了初始化以后女阀,a對(duì)象則進(jìn)行相關(guān)屬性的注入引入b的對(duì)象宅荤。完成實(shí)例化的同時(shí)a對(duì)象也會(huì)調(diào)用一次addSingleton方法,那么a對(duì)象完成以后浸策,也就只有一級(jí)緩存singletonObjects里面才有a對(duì)象冯键。
至此,屬性的循環(huán)依賴問題則完美的得到解決庸汗。
- 文末
感謝 【減肥是生命的主旋律】 的提問和回答
有一個(gè)小問題惫确,為什么在解決循環(huán)依賴問題的時(shí)候,我們會(huì)用到三級(jí)緩存singletonFactories呢蚯舱?感覺二級(jí)緩存earlySingletonObjects就可以解決問題了呢改化?
那么答案就在這里:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
if (exposedObject == null) {
return null;
}
}
}
}
return exposedObject;
}
在將三級(jí)緩存放入二級(jí)緩存的時(shí)候,會(huì)判斷是否有SmartInstantiationAwareBeanPostProcessor這樣的后置處理器枉昏,換句話說這里是給用戶提供接口擴(kuò)展的陈肛,所以采用了三級(jí)緩存。