Spring 循環引用(二)源碼分析
Spring 循環引用(二)源碼分析
Spring 系列目錄(https://www.cnblogs.com/binarylei/p/10117436.html)
Spring 循環引用相關文章:
- 《Spring 循環引用(一)一個循環依賴引發的 BUG》:https://www.cnblogs.com/binarylei/p/10325698.html
- 《Spring 循環引用(二)源碼分析》:https://www.cnblogs.com/binarylei/p/10326046.html
一、Spring 中單例 bean 的管理
Spring 對單例 bean 的管理都是在 DefaultSingletonBeanRegistry 中完成的,這裏會涉及到其內部所使用的幾個內部屬性:
// 1.1 保存最終創建成功的單例 beanName -> beanInstance private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 1.2 中間變量,beanName -> Objectfactory private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 1.3 中間變量,bean 還在創建的時候就可以獲取,用於檢測循環引用 private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
這裏涉及用於存儲 bean 的不同的 Map,可能讓人感到崩潰,簡單解釋如下:
singletonObjects
:用於保存 beanName 和創建 bean 實例之間的關系。beanName -> beanInstancesingletonFactories
:用於保存 beanName 和對象工廠的引用關系,一旦最終對象被創建(通過 objectFactory.getObject()),此引用信息將刪除。beanName -> ObjectfactoryearlySingletonObjects
:用於保存 beanName 和創建的原始 bean 的引用關系,註意這裏是原始 bean,即使用工廠方法或構造方法創建出來的對象,一旦對象最終創建好,此引用信息將刪除。 與 singletonObjects 的不同之處在於,此時 bean 還在創建過程中,而且之後還可以進行增強,也就是代理後這兩個 bean 就不是同一個了。可以通過 getBean 方法獲取到了,其目的是用來檢測循環引用。
從上面的解釋,可以看出,這 singletonFactories 和 earlySingletonObjects 都是一個臨時的輔助狀態。在所有的對象創建完畢之後,此兩個對象的 size 都為 0。那麽再來看下這兩個對象如何進行協作:
(1) 方法1:創建單例對象
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 1. 將這個 bean 添加到 singletonsCurrentlyInCreation 集合中,這樣就可以判斷 bean 是否存在創建
beforeSingletonCreation(beanName);
// 2. 初始化 bean,委托給 ObjectFactory 完成
singletonObject = singletonFactory.getObject();
// 3. 從 singletonsCurrentlyInCreation 移除該 bean
afterSingletonCreation(beanName);
// 4. 創建完成進行註冊,這樣下次就可以從緩存中直接獲取這個對象了
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
(2) 方法2:單例對象創建完成進行註冊
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);
}
}
(3) 方法3:將實例化後的對象暴露到容器中
Spring 在 bean 實例化後就會調用 addSingletonFactory 將這個對象提前暴露到容器中,這們就可以通過 getBean(A) 得到這個對象,即使這個對象仍正在創建。用於解決循環依賴。
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);
}
}
}
(4) 方法4:從緩存中獲取 bean
這個方法也是專門用於解決循環依賴的問題,當不存在循環依賴時 earlySingletonObjects 總是 null。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
二、Spring 創建 bean 過程
我們從 BeanFactory#getBean(beanName) 調用說起,看一下這幾個方法的調用順序:
2.1 AbstractBeanFactory#doGetBean
這個方法先從緩存中獲取 bean,沒有再創建 bean,因此會調用方法 4 和方法 1,我們看一下調用過程。
(1) getSingleton(beanName, true)
doGetBean 首先從緩存中獲取數據,Object sharedInstance = getSingleton(beanName)
,這個方法最終會調用 getSingleton(beanName, true)
。
(2) getSingleton(beanName, singletonFactory)
如果緩存中沒有 bean,則會調用 addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)
來創建一個新 bean,代碼如下:
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
一旦調用 getSingleton(beanName, singletonFactory) 方法,這個方法創建開始時就會標記這個 bean 為正在創建,創建結束後移除對應的標記。直接創建 bean 的過程實際上是委托給了 createBean 方法。繼續跟蹤這個方法。
2.2 AbstractAutowireCapableBeanFactory#doCreateBean
doCreateBean 方法中完成了單例的 bean 有以下幾個主要的步驟:
createBeanInstance
實例化 bean 對象,一般是通過反射調用默認的構造器。populateBean
bean 屬性註入,在這個步驟會從 Spring 容器中查找對應屬性字段的值,解決循環依賴問題。initializeBean
調用的 bean 定義的初始化方法。
(3) addSingletonFactory(beanName, singletonFactory)
在 createBeanInstance 後 populateBean 前 Spring 會將這個實例化的 bean 提前暴露到容器中,這樣 populateBean 屬性註入時就可以通過 getBean(A) 查找到。
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
(4) getSingleton(beanName, false)
在 bean 初始化完成還後還需要進行依賴的檢查,這時因為提前暴露的這個 bean(即使用工廠方法或構造方法創建出來的對象) initializeBean 還可以進行增強,這樣這兩個 bean 就不是同一個了。Spring 默認是不允許這種情況發生的。
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name ‘" + beanName + "‘ has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"‘getBeanNamesOfType‘ with the ‘allowEagerInit‘ flag turned off, for example.");
}
}
}
}
(5) addSingleton(beanName, singletonObject)
在 bean 創建結束後還有一步是在 getSingleton(beanName, singletonFactory) 中完成的,調用 addSingleton(beanName, singletonObject),即註冊最終的 bean,同時清空中間的輔助狀態。
這樣單例 bean 的創建過程就完成了,下面就需要分析循環引用下 singletonFactories、earlySingletonObjects 這兩個集合的狀態。
三、循環引用下 Bean 狀態分析
3.1 正常情況
在正常的情況下,調用順序如下:以下有無,表示是否持有對指定 Bean 的引用
過程 | 方法 | singletonFactories | earlySingletonObjects | singletonObjects |
---|---|---|---|---|
緩存中獲取 | getSingleton(beanName, true) | 無 | 無 | 無 |
創建 bean | getSingleton(beanName, singletonFactory) | 無 | 無 | 無 |
提前暴露到容器中 | addSingletonFactory(beanName, singletonFactory) | 有 | 無 | 無 |
依賴檢查 | getSingleton(beanName, false) | 有 | 無 | 無 |
註冊 | addSingleton(beanName, singletonObject) | 無 | 無 | 有 |
可以看到正常情況下,單例 bean 暴露的對象只會出現在 singletonFactories 集合中,不可能出現在 earlySingletonObjects 集合中,除非在創建 bean 的過程中又調用了 getSingleton(beanName, true) 方法,也就是此時出現了循環引用。
3.2 循環引用
但是出現循環引用之後呢,就會出現這種情況:
過程 | 方法 | singletonFactories | earlySingletonObjects | singletonObjects |
---|---|---|---|---|
緩存中獲取 A | getSingleton(A, true) | A無B無 | A無B無 | A無B無 |
創建 A | getSingleton(A, singletonFactory) | A無B無 | A無B無 | A無B無 |
暴露 A 到容器中 | addSingletonFactory(A, singletonFactory) | A有B無 | A無B無 | A無B無 |
populateBean(A, mbd, instanceWrapper) A 註入 B 時又依賴了 A,此時由 B 準備解析 A…… | ||||
緩存中獲取 A | getSingleton(A, true) | A無B有 | A有B無 | A無B無 |
註冊 B | addSingleton(B, singletonObject) | A無B無 | A有B無 | A無B有 |
populateBean(A, mbd, instanceWrapper) 完成屬性註入 | ||||
A- = initializeBean(beanName, exposedObject, mbd) 在 initializeBean 之後 A 變為 A- | ||||
依賴檢查 | getSingleton(beanName, false) | A無B無 | A有B無 | A無B有 |
註冊 A | getSingleton(beanName, false) | A無B無 | A無B無 | A有B有 |
在上面這個過程中,在對 A 進行驗證時,就會從 earlySingletonObjects 中取得一個 A,但是這個 A 和後面的 A- 可能不是同一個對象,這是因為有了 beanPostProcessor 存在,它可以改變 bean 的最終值,比如對原始 bean 進行封裝,代理等。在這個過程中,出現了 3 個對象 A, A-, B,而 B 中所持有的 A 對象為原始的 A。如果這裏的 A 和 A- 不是同一個對象,即產生了 beanA 有了 beanB 的引用,但 beanB 並沒有 beanA 的引用,而是另一個 beanA 的引用。
每天用心記錄一點點。內容也許不重要,但習慣很重要!
Spring 循環引用(二)源碼分析