啥是迴圈依賴?
下面這種情況比較常見,A中注入了屬性B,B中注入了A屬性。
@Component
public class A {
@Autowired
private B b; //在A中注入B
}
@Component
public class B {
@Autowired
private A a; //在B中注入A
}
還有一種極限情況,A中注入屬性A。
@Component
public class A {
@Autowired
private A a;
}
Spring可以解決迴圈依賴的條件
一、出現迴圈依賴的Bean必須是單例,原型不行。
第一點很好理解,也很好驗證,因為原型的Bean,每次獲取的時候都會建立一個新的,那麼問題來了,假設在初始化A的時候,需要注入原型的B,接著新建一個A,又新建B……無窮盡。如果真是這樣,那還得了。因此,原型情況下Spring無法解決迴圈依賴,會報錯:
aused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
二、不全是構造器注入的方式。
- 均採用setter方法注入,可以被解決。
- 全是構造器注入,無法被解決。
- setter和構造器都存在,具體情況具體分析,Spring會按照AB的順序選擇新建立哪個。為什麼先構造器不行,其實和Spring解決迴圈依賴的策略相關,後面會談到。
依賴情況 | 依賴注入方式 | 迴圈依賴是否被解決 |
---|---|---|
AB相互依賴(迴圈依賴) | 均採用setter方法注入 | 是 |
AB相互依賴(迴圈依賴) | 均採用構造器注入 | 否 |
AB相互依賴(迴圈依賴) | A中注入B的方式為setter方法,B中注入A的方式為構造器 | 是 |
AB相互依賴(迴圈依賴) | B中注入A的方式為setter方法,A中注入B的方式為構造器 | 否 |
Spring如何去解決迴圈依賴
SpringBean的建立流程
在討論Spring如何解決迴圈依賴之前,我們需要清除SpringBean的建立流程,之前的那篇文章討論了容器的啟動銷燬與物件完整的生命週期,這裡將其中涉及迴圈依賴的主要部分再做一個說明:
createBeanInstance
:例項化,其實也就是呼叫物件的構造方法或者工廠方法例項化物件populateBean
:填充屬性,這一步主要是對bean的依賴屬性進行注入(@Autowired
)initializeBean
:回撥執行initMethod
、InitializingBean
等方法
可以想到,對於單例的bean,在createBeanInstance的時候,應該沒啥問題,迴圈依賴的問題應該發生在第二步屬性注入的時候,而這時後這個例項的狀態,正好處於:已經例項化,還未初始化的中間狀態。這一點非常關鍵!!!!
Spring維護的三級快取
在DefaultSingletonBeanRegistry
類中,維護了三個註釋以Cache of
開頭的Map,通過反省可以注意到,三級快取與前兩級快取不太一樣,Map中維護的值是ObjectFactory型別。
//單例快取池 beanName - instance 一級快取
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//bean的早期引用, bean name to bean instance 二級快取
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
//單例工廠 beanName - ObjectFactory 三級快取
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
singletonObjects
:一級快取,一個單例bean【例項化+初始化】都完成之後,將會加入一級快取,也就是我們俗稱的單例池。earlySingletonObjects
:二級快取,用於存放【例項化完成,還沒初始化】的例項,提前暴露,用於解決迴圈依賴問題。singletonFactories
:三級快取,存放單例物件工廠ObjectFactory,與二級快取不同的是,它可以應對產生代理物件。
@FunctionalInterface //函式式介面
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
還有幾個比較重要的集合:
//bean被建立完成之後,註冊
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
//正在建立過程中的bean待的地兒,bean在開始建立的時候放入,知道建立完成將其移除
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
getSingleton
AbstractBeanFactory.doGetBean中將會出現兩個過載的getSingleton方法:
protected <T> T doGetBean(...){
Object sharedInstance = getSingleton(beanName);//
// // typeCheckOnly 為 false,將當前 beanName 放入一個 alreadyCreated 的 Set 集合中。表示已經建立過一次
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
// 這個getSingleton方法非常關鍵。
//1、標註a正在建立中~
//2、呼叫singletonObject = singletonFactory.getObject();(實際上呼叫的是createBean()方法) 因此這一步最為關鍵
//3、標註此時例項已經建立完成
//4、執行addSingleton()新增進一級快取,
//同時移除二級和三級快取,還有註冊
sharedInstance = getSingleton(beanName, () -> { ... return createBean(beanName, mbd, args); });
}
getSingleton過載一號
protected Object getSingleton(String beanName, boolean allowEarlyReference)
我們的流程進行到AbstractBeanFactory#doGetBean的時候,會執行Object sharedInstance = getSingleton(beanName);
,接著會執行getSingleton(beanName,true)
,一路跟進去,最終會進到DefaultSingletonBeanRegistry 的getSingleton方法,這個方法十分重要,我們具體看一看:
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//先從一級快取中獲取,獲取到,就直接返回
Object singletonObject = this.singletonObjects.get(beanName
//如果一級快取獲取不到,且這個獲取的這個bean正在建立中,就從二級快取中獲取
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//從二級快取中獲取
singletonObject = this.earlySingletonObjects.get(beanName);
//還是獲取不到,並且allowEarlyReference為true
if (singletonObject == null && allowEarlyReference) {
//從三級快取中獲取
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//迴圈依賴第二次進入的時候,發現A 的三級快取,於是可以獲取到A 的例項,
singletonObject = singletonFactory.getObject();
//獲取到之後將其置入二級快取
this.earlySingletonObjects.put(beanName, singletonObject);
//原先的那個就從三級快取中移除
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
- 先嚐試從一級快取中獲取,如果獲取到,表示這個物件已經【初始化+例項化】全部完成,當然,對於迴圈依賴的案例來說,這一步都是獲取不到的。
- 如果一級快取中獲取不到,沒關係,看看這個bean是不是正在建立中【已經開始例項化,但還沒有初始化完全】,如果是這個情況,就嘗試從二級快取中獲取。
- 如果都獲取不到,且allowEarlyReference為true的時候,從三級快取中取,三級快取中存放的是ObjectFactory。
getSingleton過載二號
另外一個Singleton過載的方法:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//將beanName放入到singletonsCurrentlyInCreation這個集合中,標誌著這個單例Bean正在建立
beforeSingletonCreation(beanName);
boolean newSingleton = false;
// 傳入的lambda在這裡會被執行,呼叫createBean方法建立一個Bean後返回
singletonObject = singletonFactory.getObject();
newSingleton = true;
singletonObject = this.singletonObjects.get(beanName);
}
// 建立完成後將對應的beanName從singletonsCurrentlyInCreation移除
afterSingletonCreation(beanName);
}
if (newSingleton) {
//加入一級快取
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
addSingleton
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
//加入一級快取
this.singletonObjects.put(beanName, singletonObject);
//三級快取移除
this.singletonFactories.remove(beanName);
//二級快取移除
this.earlySingletonObjects.remove(beanName);
//註冊一下
this.registeredSingletons.add(beanName);
}
}
addSingletonFactory
AbstractAutowireCapableBeanFactory#doCreateBean
在物件例項化完成,初始化之前進行:
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean. 例項化
BeanWrapper instanceWrapper = null;
// 呼叫構造器或工廠方法 例項化
instanceWrapper = createBeanInstance(beanName, mbd, args);
////我們通常說的bean例項,bean的原始物件,並沒有進行初始化的物件 A{ b:null}
Object bean = instanceWrapper.getWrappedInstance();
//表示是否提前暴露原始物件的引用,對於單例的bean,一般來說為true, 可以通過allowCircularReferences關閉迴圈引用解決迴圈依賴問題
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));
//是否允許單例提前暴露
if (earlySingletonExposure) {
//呼叫這個方法,將一個ObjectFactory放進三級快取,二級快取會對應刪除
//getEarlyBeanReference方法: 1、如果有SmartInstantiationAwareBeanPostProcessor,呼叫他的getEarlyBeanReference方法,2、如果沒有,則不變還是,exposedObject
//這裡也是AOP的實現之處,AbstractAutoProxyCreator implements SmartInstantiationAwareBeanPostProcessor
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));//在bean例項化後,屬性注入之前,Spring將bean包裝成一個工廠新增進三級快取中
}
//此時bean已經例項化完成, 開始準備初始化
// bean為原始物件
Object exposedObject = bean;
try {
//負責屬性的裝配(如依賴注入),遇到迴圈依賴的情況,會在內部getBean("b")->getSingleton(b)
populateBean(beanName, mbd, instanceWrapper);
//處理bean初始化完成後的各種回撥這裡有可能返回一個代理物件
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
//如果bean允許被早期暴露,進入程式碼
if (earlySingletonExposure) {
//第二引數為false表示不會從三級快取中在檢查,最多從二級快取中找,其實二級快取就夠了,其實之前getSingleton的時候,已經觸發了A 的ObjectFactory.getObject(),A例項已經放入二級快取中
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
//如果沒有代理,進入這個分支
if (exposedObject == bean) {
exposedObject = earlySingletonReference; /
}
}
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
//如果一級快取中尚未存在
if (!this.singletonObjects.containsKey(beanName)) {
//新增到三級快取中
this.singletonFactories.put(beanName, singletonFactory);
//從二級快取中移除
this.earlySingletonObjects.remove(beanName);
//註冊一下
this.registeredSingletons.add(beanName);
}
}
}
getEarlyBeanReference
前面談到了這個方法,還沒有細說:
//是否允許單例提前暴露
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
它實際上就是呼叫了後置處理器的getEarlyBeanReference
,而真正實現了這個方法的後置處理器只有AbstractAutoProxyCreator
,與Aop相關,也就是說,在不考慮Aop的情況下,這個方法壓根就和沒呼叫似的。這裡我們也能更加明確,三級快取出現很大程度上也是為了更好處理代理物件。
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
//呼叫後值處理器的getEarlyBeanReference
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
我們可以跟進去看一看:
//AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
//如果需要的話,返回一個代理物件
return wrapIfNecessary(bean, beanName, cacheKey);
}
那麼如果考慮可能會存在代理物件出現,這時三級快取中存在的就是這個代理物件,並且之後通過getSingleton從三級快取中取出,放入二級快取中的也是這個物件。
解決迴圈依賴的流程
本質其實就是 讓A注入B,B注入A ,B先注入的是一個還沒初始化就提前用的A 的引用。【這裡不考慮AOP】
以開頭的A,B為例,假設他們都使用屬性欄位注入:
-
A首先getBean,試圖獲取容器中單例A,第一次容器中還不存在,於是就需要開始建立A。
-
一頓操作,落點:A此時已經被例項化完成,但是還沒有初始化,緊接著將A與一個ObjectFactory存入三級快取 。如果A被AOP代理,通過這個工廠獲取到的就是A代理後的物件,如果沒有代理,工廠最後獲取到的就是A 的例項化物件。
-
初始化A,意為A的屬性賦值,這時發現B需要注入,於是getBean,來一遍相同的步驟。
-
一頓操作,落點:B此時已經被例項化完成,但是還沒有初始化,緊接著將B與一個ObjectFactory存入三級快取 。
-
初始化B,發現需要注入A,於是getBean("a"),此時它在三級快取中找到了A與
ObjectFactory<?> singletonFactory
,通過singletonFactory.getObject();
得到A的引用。並將其存入二級快取,且從三級快取移除 。 -
B注入從物件工廠獲得的A的引用,此時B已經初始化完成【代表已經注入A成功,其實是擁有了A的引用】,將B加入到一級快取,並將B在二級快取、三級快取中的玩意清除,返回。
-
剛剛是A初始化到一半切出來開始例項化B的,那麼接下來也應該返回到A的初始化流程中去。
-
顯然B都已經初始化完畢了,A當然也順利地初始化成功了,同樣,也將A加入一級快取中,並將A在二級快取、三級快取中清除。
-
至此,Spring解決迴圈依賴結束,A與B都已例項化+初始化完成,並存入一級快取,且二級快取、三級快取中已經沒有了A和B。
當然了,這個過程其實是在例項化A的時候,把B一併例項化了,於是在遍歷BeanNames例項化B的時候,就不需要進行這麼複雜的操作了,因為一級快取中已經存在B了。
為什麼先用構造器注入不能解決迴圈依賴?
原因在於,Spring解決迴圈依賴其實是在Bean已經例項化但未初始化這個中間狀態的時候進行處理的,因此bean的例項化與初始化兩個操作必須分開,才有機會存入三級快取,提前暴露原始物件。
但是如果使用如果A先使用構造器,在注入的時候,他會去找B,B再注入A,可此時A並沒有暴露,也就失敗了。
但如果A先用setter注入,A會先暴露,再注入B,B再注入A的時候,就可以通過三級快取拿到A了。
僅用一級快取可以解決迴圈依賴麼?
顯然不能,Spring通過多個快取達到儲存不同狀態的物件:
- 例項化和初始化都已經完成。
- 已經例項化,但還沒初始化。
如果只有一級快取,併發情況下,可能取到例項化但未初始化的物件。
為什麼需要三級快取,直接二級暴露引用不行麼?
三級快取使用的是工廠,而不是引用,原因在於:https://mp.weixin.qq.com/s/kS0K5P4FdF3v-fiIjGIvvQ
延遲隊例項化階段生成的物件的代理,只有真正發生迴圈依賴的時候,才去提前生成代理物件,否則只會建立一個工廠並將其放入到三級快取中,但是不會去通過這個工廠真正建立物件。
答:這個工廠的目的在於延遲對例項化階段生成的物件的代理,只有真正發生迴圈依賴的時候,才去提前生成代理物件,否則只會建立一個工廠並將其放入到三級快取中,但是不會去通過這個工廠去真正建立物件
我們思考一種簡單的情況,就以單獨建立A為例,假設AB之間現在沒有依賴關係,但是A被代理了,這個時候當A完成例項化後還是會進入下面這段程式碼:
// A是單例的,mbd.isSingleton()條件滿足 // allowCircularReferences:這個變數代表是否允許迴圈依賴,預設是開啟的,條件也滿足 // isSingletonCurrentlyInCreation:正在在建立A,也滿足 // 所以earlySingletonExposure=true boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); // 還是會進入到這段程式碼中 if (earlySingletonExposure) { // 還是會通過三級快取提前暴露一個工廠物件 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); }
看到了吧,即使沒有迴圈依賴,也會將其新增到三級快取中,而且是不得不新增到三級快取中,因為到目前為止Spring也不能確定這個Bean有沒有跟別的Bean出現迴圈依賴。
假設我們在這裡直接使用二級快取的話,那麼意味著所有的Bean在這一步都要完成
AOP
代理。這樣做有必要嗎?不僅沒有必要,而且違背了Spring在結合
AOP
跟Bean的生命週期的設計!Spring結合AOP
跟Bean的生命週期本身就是通過AnnotationAwareAspectJAutoProxyCreator
這個後置處理器來完成的,在這個後置處理的postProcessAfterInitialization
方法中對初始化後的Bean完成AOP
代理。如果出現了迴圈依賴,那沒有辦法,只有給Bean先建立代理,但是沒有出現迴圈依賴的情況下,設計之初就是讓Bean在生命週期的最後一步完成代理而不是在例項化後就立馬完成代理。
總結
圖片來源:https://blog.csdn.net/f641385712/article/details/92801300
Spring通過三級快取解決了迴圈依賴:
singletonObjects
:一級快取,一個單例bean【例項化+初始化】都完成之後,將會加入一級快取,也就是我們俗稱的單例池。earlySingletonObjects
:二級快取,用於存放【例項化完成,還沒初始化】的例項,提前暴露,用於解決迴圈依賴問題。singletonFactories
:三級快取,存放單例物件工廠ObjectFactory,與二級快取不同的是,它可以應對產生代理物件。
Spring不能夠解決先用構造器注入情況的迴圈依賴,原因在於Spring解決迴圈依賴的關鍵在於bean例項例項化完成,初始化之前的狀態,將其加入三級快取,提前暴露bean。
最後,迴圈依賴應當在編碼的時候就考慮去儘量避免,如果避免不了,那就儘量不要使用構造器注入,可以使用欄位注入。
有點暈了,本來想簡單地學習一下,沒想到一套接著一套,頭暈眼花,還是程式碼看的太少了,繼續努力。感覺有點亂,如果有說的不對的地方,還望評論區指點一二!!抱拳!!!
參考資料: