1. 循環依賴
循環依賴其實就是循環引用,也就是兩個或則兩個以上的bean互相持有對方,最終形成閉環。比如A依賴於B,B依賴於C,C又依賴於A。如下圖:
注意,這裏不是函數的循環調用,是對象的相互依賴關係。循環調用其實就是一個死循環,除非有終結條件。
循環依賴就是N個類中循環嵌套引用,如果在日常開發中我們用new 對象的方式發生這種循環依賴的話程序會在運行時一直循環調用,直至內存溢出報錯。
2. Spring循環依賴的場景
常規Java的循環依賴有兩個場景:
- 構造器的循環依賴。
- field屬性的循環依賴。
如果是Spring的依賴注入場景的話,field屬性的循環依賴還可以分爲
- 單例bean的循環依賴(scope=singleton)
- 非單例bean的循環依賴(scope=prototype)。
2.1 構造器的循環依賴
構造器的循環依賴代碼如下:
@Service
public class A {
public A(B b) { }
}
@Service
public class B {
public B(C c) {
}
}
@Service
public class C {
public C(A a) { }
}
我們如果啓動Spring的初始化流程,最後執行得到的報錯是:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
2.2 屬性的循環依賴
屬性的依賴,我們都知道是Spring利用反射,調用了對應屬性的setter方式進行注入的。
2.2.1 單例屬性的循環依賴
我們知道,Spring中@Service和@Autowired註解都是默認的單例模式,即scope=singleton。
@Service
public class A1 {
@Autowired
private B1 b1;
}
@Service
public class B1 {
@Autowired
public C1 c1;
}
@Service
public class C1 {
@Autowired public A1 a1;
}
結果:項目啓動成功。
2.2.1 原型屬性的循環依賴
ok,單例屬性的循環依賴是被允許的,那麼原型模式呢?我們添加一個scope=prototype的註解:
@Service
@Scope("prototype")
public class A1 {
@Autowired
private B1 b1;
}
@Service
@Scope("prototype")
public class B1 {
@Autowired
public C1 c1;
}
@Service
@Scope("prototype")
public class C1 {
@Autowired public A1 a1;
}
結果:項目啓動失敗,發現了一個cycle:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
3 原因分析
3.1 bean的初始化
在Spring中,同樣對於循環依賴的場景,構造器注入和prototype類型的屬性注入都會初始化Bean失敗。只有單例的屬性注入是可以成功的,這是爲什麼呢?
原因就藏在Spring IOC的源碼中。
我們知道Bean的初始化流程如下圖所示:
這裏面最重要的是如下三步:
分別對應Spring源碼中的這三個方法:
-
createBeanInstance()方法:實例化,其實也就是調用對象的構造方法實例化對象,或者說Spring利用反射,new了一個對象。
-
populateBean()方法:填充屬性,這一步主要是多bean的依賴屬性進行填充。
-
initializeBean()方法:調用spring xml中的init方法,不過這個和循環依賴無關,我們不多解釋。
可以看到,Spring是先將Bean對象實例化之後再設置對象屬性的。
3.2 三級緩存
我們知道了在Spring中對象實例化和對象屬性填充是分成兩步來操作的,爲了解決循環依賴,Spring內部維護了三個Map,也就是我們通常說的三級緩存。
筆者翻閱Spring文檔倒是沒有找到三級緩存的概念,可能也是本土爲了方便理解的詞彙。
三級緩存在DefaultListableBeanFactory
類中(繼承自其父類DefaultSingletonBeanRegistry
):
/** Cache of singleton objects: bean name --> bean instance(緩存單例實例化對象的Map集合) 一級緩存*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
/** Cache of singleton factories: bean name --> ObjectFactory(單例的工廠Bean緩存集合) 三級緩存*/
private final Map<String, ObjectFactory> singletonFactories = new HashMap<String, ObjectFactory>(16);
/** Cache of early singleton objects: bean name --> bean instance(早期的單身對象緩存集合) 二級緩存*/
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
因爲在Spring中,對象實例化和對象屬性填充是分成兩步來操作的,那麼很顯然,一個bean可以被分成兩個階段,名字是我自己取的:
- 胚胎階段:即已經new出了一個對象,但是還沒完成populateBean()方法,依賴的屬性還未填充完畢的階段。
- 成熟階段:即完成populateBean()方法,依賴的屬性已經填充完畢的階段,此時對象就已經是一個成熟的對象了。
所以對應的,不同階段的bean,會被存放在不同級別的緩存中,三級緩存因此而來:
- singletonFactories:三級緩存,保存胚胎階段對象的工廠類。
- earlySingletonObjects:二級緩存,保存胚胎階段的對象。
- singletonObjects:一級緩存,俗稱單例池或者容器。構造完成的成熟階段的單例對象都在裏面。
這三個緩存中,三級緩存和循環依賴息息相關,那麼Spring如何利用三級緩存來解決循環依賴呢?我們來理一下整個初始化bean的全過程。
Spring初始化容器對象的代碼在org.springframework.context.support.AbstractApplicationContext#refresh()
中方法,它調用finishBeanFactoryInitialization(beanFactory)
方法,進而調用了org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons()
方法,該方法顧名思義,負責遍歷註冊的beanName,依次初始化所有非懶加載的單例bean。
public void preInstantiateSingletons() throws BeansException {
...
...
List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
// 將獲取到的所有bean依次初始化
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
// 初始化的關鍵方法,getBean()方法
final FactoryBean factory = (FactoryBean) getBean(FACTORY_BEAN_PREFIX + beanName);
...
}
else {
getBean(beanName);
}
}
}
...
...
}
**AbstractBeanFactory.getBean(beanName)**是核心方法,它調用的是AbstractBeanFactory.doGetBean(beanName)方法,初始化bean的邏輯就在其中。
@SuppressWarnings("unchecked")
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
// 從三級緩存中取出bean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
if (logger.isDebugEnabled()) {
// 檢查當前 Bean是否正處於正在創建的狀態(新生階段)中,當Bean創建時會將Bean名稱存放到singletonsCurrentlyInCreation集合中
if (isSingletonCurrentlyInCreation(beanName)) {
...
}
...
}
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}else{
...
...
// Create bean instance.
// 如果該bean是單例
if (mbd.isSingleton()) {
// 處理
sharedInstance = getSingleton(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
...
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
...
}
...
return (T) bean;
}
其中,重點的方法DefaultSingletonBeanRegistry.getSingleton(beanName),我們可以看下邏輯:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 首先先嚐試從singletonObjects緩存裏面獲取單例bean對象
Object singletonObject = this.singletonObjects.get(beanName);
// 如果緩存中沒獲取到單例bean對象
if (singletonObject == null) {
synchronized (this.singletonObjects) {
// 嘗試從earlySingletonObjects緩存裏面獲取早期單例bean對象
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 如果earlySingletonObjects緩存裏面也沒有早期bean對象,那麼從factory緩存裏獲取工廠類。
ObjectFactory singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 利用工廠類生成一個單例bean對象
singletonObject = singletonFactory.getObject();
// 將剛創建的早起bean對象,放進earlySingletonObjects緩存
this.earlySingletonObjects.put(beanName, singletonObject);
// 從singletonFactories中移除,結合放入earlySingletonObjects中的操作。其實也就是從三級緩存移動到了二級緩存。
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
doGetBean()方法裏面邏輯很多,我們簡單描述其調用堆棧(只列出和循環依賴相關的堆棧):
- 1 DefaultSingletonBeanRegistry.getSingleton(beanName)
- 1.1【嘗試從一級緩存取中bean,如果取到就返回】
- 1.2【如果取不到,就加鎖,從二級緩存取,如果取到就返回】
- 1.3【如果取不到,再從三級緩存中取到ObjectFactory對象,如果取到了】
- 1.3.1 ObjectFactory.getObject() // 通過factory類獲取bean對象
- 1.3.2【從三級緩存中刪除該bean的工廠類,並將得到的bean對象加入二級緩存】
- 1.4【如果取不到ObjectFactory對象,返回null】
- 2 【如果getSingleton(beanName)返回的null】
- 【如果該bean是單例】
- DefaultSingletonBeanRegistry.getSingleton(beanName,factory) // 該方法負責實例化bean。factory的getObject()調用createBean
- ①【將當前bean放入singletonsCurrentlyInCreation這個Set中,表示該bean正在創建】
- ② ObjectFactory.getObject() // 調用工廠方法,獲取bean對象
- AbstractAutowireCapableBeanFactory.createBean() // 創建對象
- AbstractAutowireCapableBeanFactory.doCreateBean() // 實際方法
- ⑴ AbstractAutowireCapableBeanFactory.createBeanInstance()
- AbstractAutowireCapableBeanFactory.instantiateBean() // 實例化bean
- ⑵ 【如果當前bean是創建中的(當前bean是否在singletonsCurrentlyInCreation中來判斷)單例bean,且Spring配置支持循環依賴】
- DefaultSingletonBeanRegistry.addSingletonFactory() // 將bean加入三級緩存。factory的getObject()調用getEarlyBeanReference
- ⑶ AbstractAutowireCapableBeanFactory.populateBean() // 填充依賴的屬性
- AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues() // 對屬性進行賦值
- AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement.inject() // 依賴注入
- AbstractBeanFactory.getBean(B)
- ...
- ...
- ...
- AbstractBeanFactory.getBean(B)
- AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement.inject() // 依賴注入
- AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues() // 對屬性進行賦值
- ⑴ AbstractAutowireCapableBeanFactory.createBeanInstance()
- AbstractAutowireCapableBeanFactory.doCreateBean() // 實際方法
- AbstractAutowireCapableBeanFactory.createBean() // 創建對象
- ③【將當前bean從singletonsCurrentlyInCreation這個Set中刪除,表示該bean完成創建】
- ④【將當前bean加入一級緩存中,並且在二級三級緩存中刪除該bean】
- 【如果該bean是單例】
如果populateBean()方法中A bean依賴了B bean,那麼就會進入AbstractBeanFactory.getBean(B)的邏輯,於是,整個流程如下圖:
3.3 三種循環依賴的總結
所以我們可以看到,單例的屬性注入流程中有兩個重點,就是這兩個點,解決了循環依賴:
-
提前曝光,如果用c語言的說法就是將指針曝光出去,用java就是將引用對象曝光出去。也就是說即便a對象還未創建完成,但是在實例化過程中new A()動作完成後,A bean就已經被放進了緩存之中,接下來B bean就可以引用的到。
-
已經瞭解了提前曝光的作用,而相比而言曝光的時機也非常的重要,該時機發生在實例化之後,填充屬性初始化之前。
正是因爲屬性注入(或者說set方法注入)時,實例化和初始化是分開的兩步,所以才能讓Spring有可乘之機,在這兩個步驟之間做提前曝光,這纔有了Spring能夠支持set方法注入時循環依賴的結論。
而構造器的循環依賴Spring之所以不支持,也正是因爲此時實例化和初始化是原子的一個步驟,沒有辦法在中間插入提前曝光的機會。
構造器注入的報錯如下圖:
至於原型模式下的循環依賴,其實很好理解,因爲原型模式每次都是重新生成一個全新的bean,根本沒有緩存一說。這將導致實例化A完,填充發現需要B,實例化B完又發現需要A,而每次的A又都要不一樣,所以死循環的依賴下去。
唯一的做法就是利用循環依賴檢測,發現原型模式下存在循環依賴並拋出異常。
AbstractBeanFactory工廠類有個Set,叫做prototypesCurrentlyInCreation,它和前文中描述的singletonsCurrentlyInCreation一樣,用來存放正在創建中的bean對象,只不過前者存的是原型模式的bean,後者存的是單例模式的bean。
Spring會在實例化prototype bean後將其放入prototypesCurrentlyInCreation中,如果有循環依賴,就會檢查被依賴的bean是否也在prototypesCurrentlyInCreation中,如果是,那就表示依賴的bean和被依賴的bean同時在創建中,那就發生了循環依賴,這是不允許的。
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
3.4 爲什麼要有三級緩存
經過前文的敘述,我們貌似發現,三級緩存和二級緩存,貌似作用有點重複,兩級緩存不夠嗎,一級緩存不夠嗎?爲什麼要用三級緩存?
只用一級緩存肯定不行,這很好理解,一級緩存的問題在於,就一個map,裏面既有完整的bean,也有不完整的,尚未設置屬性的bean。如果這時候,有其他線程獲取到了不完整的bean,並且對還是null的屬性做操作,那就直接空指針了。
那麼兩級緩存夠嗎?其實是夠的,IoC循環依賴,兩級緩存就夠用了。
但是,如果參與循環依賴的A和B中,至少有一個對象有AOP切面呢?(AOP切面會動態生成一個代理對象,依賴注入的實際上得是代理對象纔行)
在考慮有AOP動態代理對象存在的情況下,兩級緩存就不夠用了,假設我們給A加了個切面,Spring給A生成了一個動態代理對象A_Proxy。
如果只有兩級緩存,一級緩存放完成初始化的bean,二級緩存放提前曝光的早期bean。那麼
- A完成實例化之後將引用提前曝光至二級緩存,並開始初始化B,
- B發現要依賴A,就會從二級緩存中取出A對象,注入屬性。此時B就會錯誤的引用了A,而不是Spring希望的引用A_Proxy。
那三級緩存就能解決這個問題麼?可以的,還記得我們的第三級緩存存放的是工廠類ObjectFactory。當三級緩存命中的時候,我們是調用ObjectFactory.getObject()來獲取對象的,而getObject()實際調用的又是各個beanPostProcessor的getEarlyBeanReference()方法:
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
其中,主要就是AOP的主力beanPostProcessor,AbstractAutoProxyCreator#getEarlyBeanReference
:
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;
// AbstractAutoProxyCreator的getEarlyBeanReference
// 調用了SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
if (exposedObject == null) {
return exposedObject;
}
}
}
}
return exposedObject;
}
在看SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference():
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.add(cacheKey);
// 此處,會創建代理,於是返回的對象,就是個代理對象了
return wrapIfNecessary(bean, beanName, cacheKey);
}
這就能保證如果有動態代理的情況,那麼從三級緩存取出來的對象,就會是代理對象A_Proxy。
我們把doCreateBean的流程串起來走一下,只列出相關的代碼,並假設A和B循環依賴,且A有AOP切面,我們稱原始的A爲A_Origin,A的代理對象爲A_Proxy:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
...
if (instanceWrapper == null) {
// 創建A_Origin對象,此時,屬性什麼的全是null,可以理解爲,只是new了,field還沒設置
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);
...
// 如果該bean是單例且允許Spring配置允許循環依賴,且當前bean確實在創建中
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
...
// 添加到第三級緩存;加進去的,只是個factory,只有循環依賴的時候,纔會發揮作用
addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
// 把A_Origin,存到exposedObject
Object exposedObject = bean;
try {
// 填充屬性;循環依賴情況下,A/B循環依賴。當前爲A_Origin,那麼此時填充A的屬性的時候,會去:new B;
// 並且因爲三級緩存機制,B中引用的A是A_Proxy。然後B初始化完成,邏輯返回,繼續A_Origin的初始化,A_Origin引用了B。
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
// 對A_Origin進行後置處理,此時調用aop後置處理器的postProcessAfterInitialization;
// 這裏底層是有可能調用wrapIfNecessary獲取到代理對象。
// 不過Spring爲了防止wrapIfNecessary被重複調用,從而生成不同的A_Proxy對象,
// 所以對於一個bean而言,Spring會記下已經調用了wrapIfNecessary的beanName,保證wrapIfNecessary只會被調用一次。
// 因爲在B的初始化中,A的wrapIfNecessary已經被調用了,所以在這裏,wrapIfNecessary不會被調用
// 所以此處返回的是A_Origin
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
...
if (earlySingletonExposure) {
// 去三級緩存裏獲取A,拿到的是A_Proxy
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 此時exposedObject=A_Origin, bean=A_Origin, earlySingletonReference=A_Proxy
// 所以下面這個條件是滿足的,所以,exposedObject,最終被替換爲A_Proxy:
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
...
}
}
...
return exposedObject;
}
讀到這裏,也許有人會問,就算只使用兩級緩存,我如果在A實例化後,緊接着就調用getEarlyBeanReference()方法去創建切面,然後將生成的A_Proxy放入二級緩存行不行?這不是又可以避免代理對象的問題,又只需要兩級緩存嗎?
答案是:理論上,是的,可以,但性能不好。
因爲Spring中循環依賴出現場景很少,我們沒有必要爲了解決系統中那1%可能出現的循環依賴問題,而讓99%的bean在創建時都去調用getEarlyBeanReference()走上這麼一圈。大部分bean調用getEarlyBeanReference(),只會徒增判斷邏輯,而沒有實質的作用,他們既沒有切面,也沒有配置相關的BeanPostProcessor類。
使用三級緩存,就可以讓確實有循環依賴場景的bean纔會去調用getEarlyBeanReference()。因爲只有有循環依賴場景的bean,纔會用到二三級緩存。
而正常的bean都是
實例化——加入三級緩存——注入屬性——執行init方法——執行BeanPostProcessor的方法——加入一級緩存——刪除三級緩存——完成初始化
這樣的流程。三級緩存的增刪,只是一個以防萬一而已。