方案導入
循環依賴是什么
構造出兩個對象A和B,A中有成員B,B中有成員A,換成代碼就是這樣子。
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
循環依賴會帶來什么問題
如果創建A對象,那必須注入B對象,注入B對象又需要創建A對象,如此反復,直到OOM

如何解決循環依賴問題
我們想象有這樣一幅有向圖,我們從A開始,到D結束,當執行到D的時候,D還是會繼續執行,因為D不知道A是否被經過。

為了解決這一問題,我們只需要將經過的路徑點染色就好了。

D發現A已經是紅色,知道已經被途徑過,主動結束循環。
我們很容易就能發現,循環依賴問題和圖路徑中是否有環問題是一樣的,就能保證Bean(實例)不被重復創建。

聯系Spring
都說Spring三級緩存解決了循環依賴問題,那我們就使用了一級緩存就解決了緩存依賴問題,spring的開發團隊怎么會傻到用三級緩存解決問題,當然這句話可能還有一個歧義,第三層緩存區解決了緩存依賴問題,這同樣也是錯的,且聽下文分析。
三級緩存是什么
// 存儲單例的Bean對象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 存儲早期曝光的單例Bean對象,只是一個Bean的引用,未初始化
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 存儲單例Bean對象的工廠
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
三級緩存如何工作 - 源碼部分
我會對關鍵代碼打注釋,你這么聰明肯定一下就看懂了
當我們獲取想要注入的單例時,有以下代碼,刪去了 try - catch的異常處理和解決并發問題的代碼塊,便于清晰的閱讀
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//從一級緩存中獲取Bean
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//從二級緩存中獲取Bean
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
//從三級緩存(工廠)中獲取Bean
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
//從工廠中獲取到 singletonObject 時,從工廠緩存中刪去工廠,工廠創建的對象加入二級緩存
if (this.singletonFactories.remove(beanName) != null) {
this.earlySingletonObjects.put(beanName, singletonObject);
}
}
}
}
return singletonObject;
}
可見 getSingleton 的路徑是 一級緩存 → 二級緩存 → 三級緩存,同時當從三級緩存中獲取到早期對象時,直接放入二級緩存,刪除三級緩存(后續的多次引用也是二級緩存),可見二級緩存+短暫的三級緩存相當于標記bean為已實例化,所以依賴三級緩存解決循環依賴顯然是錯的
那三級緩存(工廠)到底存儲著什么,不是二級緩存就能解決問題了嗎?我們在 doCreateBean 中可以看到以下代碼。
// 當前 Bean(例如 A)在實例化后、依賴注入和初始化完成前,是否需要將其作為“早引用”暴露給其他 Bean
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
// 允許被早引用(早期曝光)
if (earlySingletonExposure) {
// 添加到單例工廠(三級緩存)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 被三級緩存添加后再進行初始化
Object exposedObject = bean;
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
證明了三級緩存以及二級緩存中的對象是引用對象,未被真正初始化,等于是一個懶加載,同樣也不會造成循環依賴,因為其內部的對象沒有只引用了實例化的對象,未被初始化。
getEarlyBeanReference 應該有關于Factory中的一些信息
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
// 是否被代理(AOP,字節碼增強)
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
倘若沒有經過字節碼增強代碼可以縮略成兩行
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
return exposedObject;
}
誒臥槽,這不是啥也沒干!
三級緩存只參與了AOP對象的返回,解決bean的AOP代理問題
下圖展示了bean的初始化過程

那我們現在可以得出以下結論了:
- 第一級緩存用來簡單返回緩存后的bean對象。
- 第二級緩存就可以解決循環依賴問題。
- 二三級緩存只保留了實例化的bean,未初始化,不會導致循環依賴。
- 第三級緩存用來解決Bean的代理類的實例化問題。
如果這篇文章對你有所幫助,請給我點個贊。你的肯定會讓我非常開心。??????
求點贊 orz OTZ !!
浙公網安備 33010602011771號