Spring原始碼分析之回圈依賴及解決方案
往期文章:
- Spring原始碼分析之預啟動流程
- Spring原始碼分析之BeanFactory體系結構
- Spring原始碼分析之BeanFactoryPostProcessor呼叫程序詳解
- Spring原始碼分析之Bean的創建程序詳解
正文:
首先,我們需要明白什么是回圈依賴?簡單來說就是A物件創建程序中需要依賴B物件,而B物件創建程序中同樣也需要A物件,所以A創建時需要先去把B創建出來,但B創建時又要先把A創建出來...死回圈有木有...

那么在Spring中,有多少種回圈依賴的情況呢?大部分人只知道兩個普通的Bean之間的回圈依賴,而Spring中其實存在三種物件(普通Bean,工廠Bean,代理物件),他們之間都會存在回圈依賴,這里我給列舉出來,大致分別以下幾種:
- 普通Bean與普通Bean之間
- 普通Bean與代理物件之間
- 代理物件與代理物件之間
- 普通Bean與工廠Bean之間
- 工廠Bean與工廠Bean之間
- 工廠Bean與代理物件之間
那么,在Spring中是如何解決這個問題的呢?
1. 普通Bean與普通Bean
首先,我們先設想一下,如果讓我們自己來編碼,我們會如何解決這個問題?
栗子
現在我們有兩個互相依賴的物件A和B
public class NormalBeanA {
private NormalBeanB normalBeanB;
public void setNormalBeanB(NormalBeanB normalBeanB) {
this.normalBeanB = normalBeanB;
}
}
public class NormalBeanB {
private NormalBeanA normalBeanA;
public void setNormalBeanA(NormalBeanA normalBeanA) {
this.normalBeanA = normalBeanA;
}
}
然后我們想要讓他們彼此都含有物件
public class Main {
public static void main(String[] args) {
// 先創建A物件
NormalBeanA normalBeanA = new NormalBeanA();
// 創建B物件
NormalBeanB normalBeanB = new NormalBeanB();
// 將A物件的參考賦給B
normalBeanB.setNormalBeanA(normalBeanA);
// 再將B賦給A
normalBeanA.setNormalBeanB(normalBeanB);
}
}
發現了嗎?我們并沒有先創建一個完整的A物件,而是先創建了一個空殼物件(Spring中稱為早期物件),將這個早期物件A先賦給了B,使得得到了一個完整的B物件,再將這個完整的B物件賦給A,從而解決了這個回圈依賴問題,so easy!
那么Spring中是不是也這樣做的呢?我們就來看看吧~
Spring中的解決方案
由于上一篇已經分析過Bean的創建程序了,其中的某些部分就不再細講了
先來到創建Bean的方法
AbstractAutowireCapableBeanFactory#doCreateBean
假設此時在創建A
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){
// beanName -> A
// 實體化A
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
// 是否允許暴露早期物件
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 將獲取早期物件的回呼方法放到三級快取中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
}
addSingletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
// 單例快取池中沒有該Bean
if (!this.singletonObjects.containsKey(beanName)) {
// 將回呼函式放入三級快取
this.singletonFactories.put(beanName, singletonFactory);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
}
ObjectFactory是一個函式式介面
在這里,我們發現在創建Bean時,Spring不管三七二十一,直接將一個獲取早期物件的回呼方法放進了一個三級快取中,我們再來看一下回呼方法的邏輯
getEarlyBeanReference
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
// 呼叫BeanPostProcessor對早期物件進行處理,在Spring的內置處理器中,并無相關的處理邏輯
// 如果開啟了AOP,將引入一個AnnotationAwareAspectJAutoProxyCreator,此時將可能對Bean進行動態代理
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
在這里,如果沒有開啟AOP,或者該物件不需要動態代理,會直接回傳原物件
此時,已經將A的早期物件快取起來了,接下來在填充屬性時會發生什么呢?
相信大家也應該想到了,A物件填充屬性時必然發現依賴了B物件,此時就將轉頭創建B,在創建B時同樣會經歷以上步驟,此時就該B物件填充屬性了,這時,又將要轉頭創建A,那么,現在會有什么不一樣的地方呢?我們看看getBean的邏輯吧
doGetBean
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly){
// 此時beanName為A
String beanName = transformedBeanName(name);
// 嘗試從三級快取中獲取bean,這里很關鍵
Object sharedInstance = getSingleton(beanName);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 從單例快取池中獲取,此時仍然是取不到的
Object singletonObject = this.singletonObjects.get(beanName);
// 獲取不到,判斷bean是否正在創建,沒錯,此時A確實正在創建
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 由于現在仍然是在同一個執行緒,基于同步鎖的可重入性,此時不會阻塞
synchronized (this.singletonObjects) {
// 從早期物件快取池中獲取,這里是沒有的
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 從三級快取中獲取回呼函式,此時就獲取到了我們在創建A時放入的回呼函式
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 呼叫回呼方法獲取早期bean,由于我們現在討論的是普通物件,所以回傳原物件
singletonObject = singletonFactory.getObject();
// 將早期物件放到二級快取,移除三級快取
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
// 回傳早期物件A
return singletonObject;
}
震驚!此時我們就拿到了A的早期物件進行回傳,所以B得以被填充屬性,B創建完畢后,又將回傳到A填充屬性的程序,A也得以被填充屬性,A也創建完畢,這時,A和B都創建好了,回圈依賴問題得以收場~

普通Bean和普通Bean之間的問題就到這里了,不知道小伙伴們有沒有暈呢~
2. 普通Bean和代理物件
普通Bean和代理物件之間的回圈依賴與兩個普通Bean的回圈依賴其實大致相同,只不過是多了一次動態代理的程序,我們假設A物件是需要代理的物件,B物件仍然是一個普通物件,然后,我們開始創建A物件,
剛開始創建A的程序與上面的例子是一模一樣的,緊接著自然是需要創建B,然后B依賴了A,于是又倒回去創建A,此時,再次走到去快取池獲取的程序,
// 從三級快取中獲取回呼函式
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 呼叫回呼方法獲取早期bean,此時回傳的是一個A的代理物件
singletonObject = singletonFactory.getObject();
// 將早期物件放到二級快取,移除三級快取
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
這時就不太一樣了,在singletonFactory.getObject()時,由于此時A是需要代理的物件,在呼叫回呼函式時,就會觸發動態代理的程序
AbstractAutoProxyCreator#getEarlyBeanReference
public Object getEarlyBeanReference(Object bean, String beanName) {
// 生成一個快取Key
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 放入快取中,用于在初始化后呼叫該后置處理器時判斷是否進行動態代理過
this.earlyProxyReferences.put(cacheKey, bean);
// 將物件進行動態代理
return wrapIfNecessary(bean, beanName, cacheKey);
}
此時,B在創建時填充的屬性就是A的代理物件了,B創建完畢,回傳到A的創建程序,但此時的A仍然是一個普通物件,可B參考的A已經是個代理物件了,不知道小伙伴看到這里有沒有迷惑呢?
不急,讓我們繼續往下走,填充完屬性自然是需要初始化的,在初始化后,會呼叫一次后置處理器,我們看看會不會有答案吧
初始化
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
//...省略前面的步驟...
// 呼叫初始化方法
invokeInitMethods(beanName, wrappedBean, mbd);
// 處理初始化后的bean
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
在處理初始化后的bean,又會呼叫動態代理的后置處理器了
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 判斷快取中是否有該物件,有則說明該物件已被動態代理,跳過
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
不知道小伙伴發現沒有,earlyProxyReferences這個快取可不就是我們在填充B的屬性,進而從快取中獲取A時放進去的嗎?不信您往上翻到getEarlyBeanReference的步驟看看~
所以,此時并未進行任何處理,依舊回傳了我們的原物件A,看來這里并沒有我們要的答案,那就繼續吧~
// 是否允許暴露早期物件
if (earlySingletonExposure) {
// 從快取池中獲取早期物件
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// bean為初始化前的物件,exposedObject為初始化后的物件
// 判斷兩物件是否相等,基于上面的分析,這兩者是相等的
if (exposedObject == bean) {
// 將早期物件賦給exposedObject
exposedObject = earlySingletonReference;
}
}
}
我們來分析一下上面的邏輯,getSingleton從快取池中獲取早期物件回傳的是什么呢?
synchronized (this.singletonObjects) {
// 從早期物件快取池中獲取,此時就拿到了我們填充B屬性時放入的A的代理物件
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 從三級快取中獲取回呼函式
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 呼叫回呼方法獲取早期bean
singletonObject = singletonFactory.getObject();
// 將早期物件放到二級快取,移除三級快取
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
發現了嗎?此時我們就獲取到了A的代理物件,然后我們又把這個物件賦給了exposedObject,此時創建物件的流程走完,我們得到的A不就是個代理物件了嗎~
此次栗子是先創建需要代理的物件A,假設我們先創建普通物件B會發生什么呢?
3. 代理物件與代理物件
代理物件與代理物件的回圈依賴是怎么樣的呢?解決程序又是如何呢?這里就留給小伙伴自己思考了,其實和普通Bean與代理物件是一模一樣的,小伙伴想想是不是呢,這里我就不做分析了,
4. 普通Bean與工廠Bean
這里所說的普通Bean與工廠Bean并非指bean與FactoryBean,這將毫無意義,而是指普通Bean與FactoryBean的getObject方法產生了回圈依賴,因為FactoryBean最終產生的物件是由getObject方法所產出,我們先來看看栗子吧~
假設工廠物件A依賴普通物件B,普通物件B依賴普通物件A,
小伙伴看到這里就可能問了,誒~你這不對呀,怎么成了「普通物件B依賴普通物件A」呢?不應該是工廠物件A嗎?是這樣的,在Spring中,由于普通物件A是由工廠物件A產生,所有在普通物件B想要獲取普通物件A時,其實最終尋找呼叫的是工廠物件A的getObject方法,所以只要普通物件B依賴普通物件A就可以了,Spring會自動幫我們把普通物件B和工廠物件A聯系在一起,
小伙伴,哦~
普通物件A
public class NormalBeanA {
private NormalBeanB normalBeanB;
public void setNormalBeanB(NormalBeanB normalBeanB) {
this.normalBeanB = normalBeanB;
}
}
工廠物件A
@Component
public class FactoryBeanA implements FactoryBean<NormalBeanA> {
@Autowired
private ApplicationContext context;
@Override
public NormalBeanA getObject() throws Exception {
NormalBeanA normalBeanA = new NormalBeanA();
NormalBeanB normalBeanB = context.getBean("normalBeanB", NormalBeanB.class);
normalBeanA.setNormalBeanB(normalBeanB);
return normalBeanA;
}
@Override
public Class<?> getObjectType() {
return NormalBeanA.class;
}
}
普通物件B
@Component
public class NormalBeanB {
@Autowired
private NormalBeanA normalBeanA;
}
假設我們先創建物件A
由于FactoryBean和Bean的創建程序是一樣的,只是多了步getObject,所以我們直接定位到呼叫getObject入口
if (mbd.isSingleton()) {
// 開始創建bean
sharedInstance = getSingleton(beanName, () -> {
// 創建bean
return createBean(beanName, mbd, args);
});
// 處理FactoryBean
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
protected Object getObjectForBeanInstance(
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
// 先嘗試從快取中獲取,保證多次從工廠bean獲取的bean是同一個bean
object = getCachedObjectForFactoryBean(beanName);
if (object == null) {
// 從FactoryBean獲取物件
object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
}
protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
// 加鎖,防止多執行緒時重復創建bean
synchronized (getSingletonMutex()) {
// 這里是Double Check
Object object = this.factoryBeanObjectCache.get(beanName);
if (object == null) {
// 獲取bean,呼叫factoryBean的getObject()
object = doGetObjectFromFactoryBean(factory, beanName);
}
// 又從快取中取了一次,why? 我們慢慢分析
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
}else{
// ...省略初始化bean的邏輯...
// 將獲取到的bean放入快取
this.factoryBeanObjectCache.put(beanName, object);
}
}
}
private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName){
return factory.getObject();
}
現在,就走到了我們自定義的getObject方法,由于我們呼叫了context.getBean("normalBeanB", NormalBeanB.class),此時,將會去創建B物件,在創建程序中,先將B的早期物件放入三級快取,緊接著填充屬性,發現依賴了A物件,又要倒回來創建A物件,從而又回到上面的邏輯,再次呼叫我們自定義的getObject方法,這個時候會發生什么呢?
又要去創建B物件...(Spring:心好累)
但是!此時我們在創建B時,是直接通過getBean在快取中獲取到了B的早期物件,得以回傳了!于是我們自定義的getObject呼叫成功,回傳了一個完整的A物件!
但是此時FactoryBean的緩沖中還是什么都沒有的,
// 又從快取中取了一次
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
}
這一次取alreadyThere必然是null,流程繼續執行,將此時將獲取到的bean放入快取
this.factoryBeanObjectCache.put(beanName, object);
從FactoryBean獲取物件的流程結束,回傳到創建B的程序中,B物件此時的屬性也得以填充,再回傳到第一次創建A的程序,也就是我們第一次呼叫自定義的getObject方法,呼叫完畢,回傳到這里
// 獲取bean,呼叫factoryBean的getObject()
object = doGetObjectFromFactoryBean(factory, beanName);
Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
if (alreadyThere != null) {
object = alreadyThere;
那么,此時this.factoryBeanObjectCache.get(beanName)能從緩沖中拿到物件了嗎?有沒有發現,拿到了剛剛B物件填充屬性時再次創建A物件放進去的!
所以,明白這里為什么要再次從快取中獲取了吧?就是為了解決由于回圈依賴時呼叫了兩次自定義的getObject方法,從而創建了兩個不相同的A物件,保證我們回傳出去的A物件唯一!
怕小伙伴暈了,畫個圖給大家

5. 工廠Bean與工廠Bean之間
我們已經舉例4種回圈依賴的栗子,Spring都有所解決,那么有沒有Spring也無法解決的回圈依賴問題呢?
有的!就是這個FactoryBean與FactoryBean的回圈依賴!
假設工廠物件A依賴工廠物件B,工廠物件B依賴工廠物件A,那么,這次的栗子會是什么樣呢?
普通物件
public class NormalBeanA {
private NormalBeanB normalBeanB;
public void setNormalBeanB(NormalBeanB normalBeanB) {
this.normalBeanB = normalBeanB;
}
}
public class NormalBeanB {
private NormalBeanA normalBeanA;
public void setNormalBeanA(NormalBeanA normalBeanA) {
this.normalBeanA = normalBeanA;
}
}
工廠物件
@Component
public class FactoryBeanA implements FactoryBean<NormalBeanA> {
@Autowired
private ApplicationContext context;
@Override
public NormalBeanA getObject() throws Exception {
NormalBeanA normalBeanA = new NormalBeanA();
NormalBeanB normalBeanB = context.getBean("factoryBeanB", NormalBeanB.class);
normalBeanA.setNormalBeanB(normalBeanB);
return normalBeanA;
}
@Override
public Class<?> getObjectType() {
return NormalBeanA.class;
}
}
@Component
public class FactoryBeanB implements FactoryBean<NormalBeanB> {
@Autowired
private ApplicationContext context;
@Override
public NormalBeanB getObject() throws Exception {
NormalBeanB normalBeanB = new NormalBeanB();
NormalBeanA normalBeanA = context.getBean("factoryBeanA", NormalBeanA.class);
normalBeanB.setNormalBeanA(normalBeanA);
return normalBeanB;
}
@Override
public Class<?> getObjectType() {
return NormalBeanB.class;
}
}
首先,我們開始創建物件A,此時為呼叫工廠物件A的getObject方法,轉而去獲取物件B,便會走到工廠物件B的getObject方法,然后又去獲取物件A,又將呼叫工廠物件A的getObject,再次去獲取物件B,于是再次走到工廠物件B的getObject方法......此時,已經歷了一輪回圈,卻沒有跳出回圈的跡象,妥妥的死回圈了,
我們畫個圖吧~

沒錯!這個圖就是這么簡單,由于始終無法創建出一個物件,不管是早期物件或者完整物件,使得兩個工廠物件反復的去獲取對方,導致陷入了死回圈,
那么,我們是否有辦法解決這個問題呢?
我的答案是無法解決,如果有想法的小伙伴也可以自己想一想哦~
我們發現,在發生回圈依賴時,只要回圈鏈中的某一個點可以先創建出一個早期物件,那么在下一次回圈時,就會使得我們能夠獲取到早期物件從而跳出回圈!
而由于工廠物件與工廠物件間是無法創建出這個早期物件的,無法滿足跳出回圈的條件,導致變成了死回圈,
那么此時Spring中會拋出一個什么樣的例外呢?
當然是堆疊溢位例外啦!兩個工廠物件一直相互呼叫,不斷開辟堆疊幀,可不就是堆疊溢位有木有~
6. 工廠物件與代理物件
上面的情況是無法解決回圈依賴的,那么這個情況可以解決嗎?
答案是可以的!
我們分析了,一個回圈鏈是否能夠得到終止,關鍵在于是否能夠在某個點創建出一個早期物件(臨時物件),而代理物件在doCreateBean時,是會生成一個早期物件放入三級快取的,于是該回圈鏈得以終結,
具體程序我這里就不再細分析了,就交由小伙伴自己動手吧~
總結
以上我們一共舉例了6種情況,通過分析,總結出這樣一條定律:
在發生回圈依賴時,判斷一個回圈鏈是否能夠得到終止,關鍵在于是否能夠在某個點創建出一個早期物件(臨時物件),那么在下一次回圈時,我們就能通過該早期物件進而跳出(打破)回圈!
通過這樣的定律,我們得出工廠Bean與工廠Bean之間是無法解決回圈依賴的,那么還有其他情況無法解決回圈依賴嗎?
有的!以上的例子舉的都是單例的物件,并且都是通過set方法形成的回圈依賴,
假使我們是由于構造方法形成的回圈依賴呢?是否有解決辦法嗎?
沒有,因為這并不滿足我們得出的定律
無法執行完畢構造方法,自然無法創建出一個早期物件,
假使我們的物件是多例的呢?
也不能,因為多例的物件在每次創建時都是創建新的物件,即使能夠創建出早期物件,也不能為下一次回圈所用!
好了,本文就到這里結束了,希望小伙伴們有所識訓~
Spring IOC的核心部分到此篇就結束了,下一篇就讓我們進行AOP之旅吧~
下文預告:Spring原始碼分析之AOP從決議到呼叫
Spring 原始碼系列
- Spring原始碼分析之 IOC 容器預啟動流程(已完結)
- Spring原始碼分析之BeanFactory體系結構(已完結)
- Spring原始碼分析之BeanFactoryPostProcessor呼叫程序(已完結)
- Spring原始碼分析之Bean的創建程序(已完結)
- Spring原始碼分析之什么是回圈依賴及解決方案
- Spring原始碼分析之AOP從決議到呼叫
- Spring原始碼分析之事務管理(上),事物管理是spring作為容器的一個特點,總結一下他的基本實作與原理吧
- Spring原始碼分析之事務管理(下) ,關于他的底層事物隔離與事物傳播原理,重點分析一下
Spring Mvc 原始碼系列
- SpringMvc體系結構
- SpringMvc原始碼分析之Handler決議程序
- SpringMvc原始碼分析之請求鏈程序
另外筆者公眾號:奇客時間,有更多精彩的文章,有興趣的同學,可以關注
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/219733.html
標籤:Java
上一篇:安裝JDK
