Spring源碼解讀『Spring Bean循環依賴』

由於Spring的IOC特性,Bean都是由Spring容器生成的,那麼如果Bean是單例的,存在兩個Bean,分別爲beanA、beanB,beanA依賴beanB,同時beanB也依賴beanA,那麼可以想象假如容器不做特殊處理的話,就會發生循環依賴,產生死鎖,Bean構造就進行不下去了。但是我們在使用時,其實並沒有關注循環依賴的問題,Spring是可以解決這種循環依賴的情況的,本篇文章我們來看一下Spring是如何解決循環依賴的。

1. Spring循環依賴示例

首先定義兩個Bean,BeanA和BeanB,兩個Bean分別由一個對方類型的成員變量,如下:

public class BeanA {
    private BeanB beanB;

    public BeanB getBeanB() {
        return beanB;
    }

    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}
public class BeanB {
    private BeanA beanA;

    public BeanA getBeanA() {
        return beanA;
    }

    public void setBeanA(BeanA beanA) {
        this.beanA = beanA;
    }
}

spring xml配置文件,配置屬性依賴:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
	http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="beanA" class="com.zhuoli.service.spring.explore.circular.dependence.BeanA">
        <property name="beanB" ref="beanB"/>
    </bean>

    <bean id="beanB" class="com.zhuoli.service.spring.explore.circular.dependence.BeanB">
        <property name="beanA" ref="beanA"/>
    </bean>

</beans>

測試代碼:

public class CircularDependenceTest {
    public static void main(String[] args) {
        AbstractApplicationContext abstractApplicationContext = new ClassPathXmlApplicationContext("classpath:spring-circular-dependence.xml");
        BeanA beanA = (BeanA) abstractApplicationContext.getBean("beanA");
        BeanB beanB = (BeanB) abstractApplicationContext.getBean("beanB");
        System.out.println(beanA.getBeanB() == beanB);
        System.out.println(beanA == beanB.getBeanA());
    }
}

運行結果:

true
true

也就是講,beanA拿到了beanB的引用,beanB同時也拿到了beanA的引用。雖然BeanA和BeanB之間存在循環依賴,但是Spring容器並沒有發生死鎖,成功解決了循環依賴問題,並構造了BeanA和BeanB對象。

2. Spring解決循環依賴

其實Spring是如何解決循環依賴問題,在上篇文章介紹Spring初始化流程的時候已經簡單提過,我們這裏突出再來理一下。同時要注意的是,我們所說的Spring解決循環依賴,只限於單例Bean,對於非單例Bean,是不支持的

簡單來講,Spring解決循依賴,其實是通過提早緩存未實例結束的bean來實現的。首先在doGetBean()方法中,該方法獲取bean,首先會嘗試從緩存中獲取,如下:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
		@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {

	final String beanName = transformedBeanName(name);
	Object bean;

	// 嘗試從緩存中獲取bean實例
	Object sharedInstance = getSingleton(beanName);
	if (sharedInstance != null && args == null) {
		//
	}

	else {
		//
	}

	// Check if required type matches the type of the actual bean instance.
	if (requiredType != null && !requiredType.isInstance(bean)) {
		//
	}
	return (T) bean;
}
public Object getSingleton(String beanName) {
	return getSingleton(beanName, true);
}

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	//1. 查詢緩存中是否有創建好的單例
	Object singletonObject = this.singletonObjects.get(beanName);
	//2. 如果緩存不存在,判斷是否正在創建中
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		//加鎖防止併發
		synchronized (this.singletonObjects) {
			// 從earlySingletonObjects中查詢是否有early緩存
			singletonObject = this.earlySingletonObjects.get(beanName);
			// early緩存也不存在,且允許early引用
			if (singletonObject == null && allowEarlyReference) {
				// 從單例工廠Map裏查詢beanName對應的ObjectFactory
				ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
				if (singletonFactory != null) {
					// 如果beanName對應的ObjectFactory存在,則調用getObject方法拿到單例對象
					singletonObject = singletonFactory.getObject();
					// 將單例對象添加到early緩存中
					this.earlySingletonObjects.put(beanName, singletonObject);
					// 移除單例工廠中對應的singletonFactory
					this.singletonFactories.remove(beanName);
				}
			}
		}
	}
	return singletonObject;
}

從以上的代碼可以看出:

  1. 緩存機制,只針對單例的bean
  2. 默認的singletonObjects緩存不存在要get的beanName時,判斷beanName是否正在創建中
  3. 從early緩存earlySingletonObjects中再查詢,early緩存是用來緩存已實例化但未組裝完成的bean
  4. 如果early緩存也不存在,從singletonFactories中查找是否有beanName對應的ObjectFactory對象工廠
  5. 如果對象工廠存在,則調用getObject方法拿到bean對象
  6. 將bean對象加入early緩存,並移除singletonFactories的對象工廠

上面最重要的就是singletonFactories何時放入了可以通過getObject獲得bean對象的ObjectFactory。考慮到循環依賴的場景,應該會是bean對象實例化後,而屬性注入之前。仔細尋找後發現,在AbstractAutowireCapableBeanFactory類的doCreateBean方法,執行完createBeanInstance實例化bean之後,populateBean屬性注入之前,有這樣一段代碼:

// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
		isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
	if (logger.isDebugEnabled()) {
		logger.debug("Eagerly caching bean '" + beanName +
				"' to allow for resolving potential circular references");
	}
	addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
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);
		}
	}
}

當判斷bean爲單例且正在創建中,而Spring允許循環引用時,將能獲得bean對象的引用的ObjectFactory添加到singletonFactories中,此時就與之前的getSingleton方法相呼應。而allowCircularReferences標識在spring中默認爲true,但是也可以通過setAllowCircularReferences方法對AbstractAutowireCapableBeanFactory進行設置。

再來看下getObject方法中的getEarlyBeanReference方法。這裏也設置了一個InstantiationAwareBeanPostProcessor後置處理器的擴展點,允許在對象返回之前修改甚至替換bean。

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	Object exposedObject = bean;
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
				SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
				exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
	}
	return exposedObject;
}

最後,來梳理一下上面abstractApplicationContext.getBean(“beanA”)的執行過程:

  1. 實例化BeanA
  2. 將能獲取BeanA對象的ObjectFactory添加到singletonFactories中
  3. BeanA注入BeanB屬性,調用getBean(“beanB”)方法
  4. 實例化BeanB
  5. 將能獲取BeanB對象的ObjectFactory添加到singletonFactories中
  6. BeanB注入BeanA屬性,調用getBean(“beanA”)
  7. 從singletonFactories中獲取ObjectFactory並調用getObject方法拿到beanA對象的引用
  8. BeanB創建完成,注入到BeanA的beanB屬性中
  9. BeanA創建完成返回

上面我們瞭解了單例的bean循環引用的處理過程,那麼多例的呢?其實我們可以按上面的思路來思考一下,單例bean的循環引用是因爲每個對象都是固定的,只是提前暴露對象的引用,最終這個引用對應的對象是創建完成的。但是多例的情況下,每次getBean都會創建一個新的對象,那麼應該引用哪一個對象呢,這本身就已經是矛盾的了。因而spring中對於多例之間相互引用是會提示錯誤的。在doGetBean protoType處理的邏輯中,第一步就存在下面的判斷:

// Fail if we're already creating this bean instance:
// We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
	throw new BeanCurrentlyInCreationException(beanName);
}

參考鏈接:

1. Spring源碼

2. bean的循環依賴

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章