前言
在實際工作中,經(jīng)常由于設計不佳或者各種因素,導致類之間相互依賴。這些類可能單獨使用時不會出問題,但是在使用Spring進行管理的時候可能就會拋出BeanCurrentlyInCreationException等異常 。當拋出這種異常時表示Spring解決不了該循環(huán)依賴,本文將簡要說明Spring對于循環(huán)依賴的解決方法。
什么是循環(huán)依賴?
循環(huán)依賴其實就是循環(huán)引用,也就是兩個或則兩個以上的bean互相持有對方,最終形成閉環(huán)。比如A依賴于B,B依賴于C,C又依賴于A。如下圖:

注意,這里不是函數(shù)的循環(huán)調用,是對象的相互依賴關系。循環(huán)調用其實就是一個死循環(huán),除非有終結條件。
Spring中循環(huán)依賴場景有:
(1)構造器的循環(huán)依賴
(2)field屬性的循環(huán)依賴。
循環(huán)依賴的產生和解決的前提
循環(huán)依賴的產生可能有很多種情況,例如:
- A的構造方法中依賴了B的實例對象,同時B的構造方法中依賴了A的實例對象
- A的構造方法中依賴了B的實例對象,同時B的某個field或者setter需要A的實例對象,以及反之
- A的某個field或者setter依賴了B的實例對象,同時B的某個field或者setter依賴了A的實例對象,以及反之
當然,Spring對于循環(huán)依賴的解決不是無條件的,首先前提條件是針對scope單例并且沒有顯式指明不需要解決循環(huán)依賴的對象,而且要求該對象沒有被代理過。同時Spring解決循環(huán)依賴也不是萬能,以上三種情況只能解決兩種,第一種在構造方法中相互依賴的情況Spring也無力回天。結論先給在這,下面來看看Spring的解決方法,知道了解決方案就能明白為啥第一種情況無法解決了。
Spring怎么解決循環(huán)依賴
Spring的循環(huán)依賴的理論依據(jù)其實是基于JAVA的引用傳遞,當我們獲取到對象的引用時,對象的field或則屬性是可以延后設置的(但是構造器必須是在獲取引用之前)。
Spring的單例對象的初始化主要分為三步:

(1)createBeanInstance:實例化,其實也就是調用對象的構造方法實例化對象
(2)populateBean:填充屬性,這一步主要是多bean的依賴屬性進行填充
(3)initializeBean:調用spring xml中的init 方法。
從上面講述的單例bean初始化步驟我們可以知道,循環(huán)依賴主要發(fā)生在第一、第二部。也就是構造器循環(huán)依賴和field循環(huán)依賴。
那么我們要解決循環(huán)引用也應該從初始化過程著手,對于單例來說,在Spring容器整個生命周期內,有且只有一個對象,所以很容易想到這個對象應該存在Cache中,Spring為了解決單例的循環(huán)依賴問題,使用了三級緩存。
主要的幾個緩存
- alreadyCreated:
- 不管單例還是原型,均會被標記,主要用在循環(huán)依賴無法解決的時候擦屁股用的。
- singletonObjects:
- 單例Bean的緩存池
- singletonFactories:
- 單例Bean在創(chuàng)建之初過早的暴露出去的Factory,為什么采用工廠方式,是因為有些Bean是需要被代理的,總不能把代理前的暴露出去那就毫無意義了。
- earlySingletonObjects:
- 執(zhí)行了工廠方法生產出來的Bean,總不能每次判斷是否解決了循環(huán)依賴都要執(zhí)行下工廠方法吧,故而緩存起來。
- singletonsCurrentlyInCreation:
- 這個很明白了,如果以上的緩存都是用來解決循環(huán)依賴的話,那么這個緩存就是用來檢測是否存在循環(huán)依賴的。
主要步驟
1、判斷該Bean是否已經(jīng)在創(chuàng)建,是則拋異常
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
2、標記該Bean已經(jīng)被創(chuàng)建,理由見下面
protected void markBeanAsCreated(String beanName) {
this.alreadyCreated.add(beanName);
}
3、初始化Bean之前提前把Factory暴露出去
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
為什么不把Bean暴露出去,而是暴露個Factory呢?因為有些Bean是需要被代理的,看下getEarlyBeanReference的實現(xiàn):
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (Iterator it = getBeanPostProcessors().iterator(); it.hasNext(); ) {
BeanPostProcessor bp = (BeanPostProcessor) it.next();
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
那什么時候執(zhí)行這個工廠方法呢?當你依賴到了該Bean而單例緩存里面有沒有該Bean的時候就會調用該工廠方法生產Bean,看下getSingleton的實現(xiàn):
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory singletonFactory = (ObjectFactory) 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);
}
現(xiàn)在有兩個疑問了,就舉個對Bean進行Wrap的操作吧,Spring是如何規(guī)避重復Wrap的呢?
1)從代碼可以看到,執(zhí)行完工廠方法會緩存到earlySingletonObjects中,因此再次調用該方法不會重復執(zhí)行Wrap的
2)對于有些BeanPostProcessor提供對Bean的Wrap的操作,但是生命周期位于在set操作之后,如果提前暴露出去被其他Bean執(zhí)行了工廠方法給Wrap起來,回過來自己再執(zhí)行BeanPostProcessor的后處理操作的時候不會發(fā)生重復嗎?
【AbstractAutoProxyCreator】
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.add(cacheKey);
return wrapIfNecessary(bean, beanName, cacheKey);
}
【AbstractAutoProxyCreator】
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
return bean;
}
這個BeanPostProcessor很典型了,用于創(chuàng)建代理類的。一般這種BeanPostProcessor總要提供一個getEarlyBeanReference的接口供其他Bean使用,而又防止了其他類直接使用到該類最原始的版本。這就是上述兩個方法如此相似的原因。置于略微的差異,你應該看出來,是防止重復執(zhí)行方法。
4、初始化Bean,執(zhí)行一個個BeanPostProcessor
protected Object initializeBean(String beanName, Object bean, RootBeanDefinition mbd) {
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware) {
((BeanClassLoaderAware) bean).setBeanClassLoader(getBeanClassLoader());
}
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(this);
}
Object wrAppedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
你不能保證這些亂七八糟的BeanPostProcessor會不會改變Bean的版本,當然,如果改變了,肯定要出錯的,在這里,Spring就沒有做依賴解決了(都給你把代理類解決了你還想啥呢),只是做了檢查,如下。
5、循環(huán)依賴解決不了的情況下的依賴檢查
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set actualDependentBeans = new LinkedHashSet(dependentBeans.length);
for (int i = 0; i < dependentBeans.length; i++) {
String dependentBean = dependentBeans[i];
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
1)當發(fā)現(xiàn)最初的Bean和ExposedObject不一致的時候(這里其實值得推敲的,只有BeanPostProcessor會導致Bean的版本更新,但是負責處理代理的BeanPostProcessor也會導致版本更新,最后豈不是和普通的BeanPostProcessor一樣走到else里面去拋異常了,詭異了!仔細想想負責處理代理的BeanPostProcessor是不會導致Bean的版本更新的,所以最后要把getSingleton取出來的最新版本的Bean賦給它,好好理解吧,真不知道怎么解釋了)就會走到else里面,看看else里面的邏輯:
2)檢查所以依賴到該Bean的哪些Bean們,如果他們已經(jīng)創(chuàng)建了,那么拋異常!這就是為什么用alreadyCreated的原因,因為原型Bean C如果依賴到了該Bean A的話,原型Bean C還能用么?當然作廢了,而且還無法解決,框架只能拋異常告訴程序員。