Spring的3級快取和迴圈引用的理解

huan1993發表於2022-04-19

此處是我自己的一個理解,防止以後忘記,如若那個地方理解不對,歡迎指出。

一、背景

在我們寫程式碼的過程中一般會使用 @Autowired 來注入另外的一個物件,但有些時候發生了 迴圈依賴,但是我們的程式碼沒有報錯,這個是什麼原因呢?

二、前置知識

1、考慮迴圈依賴的型別

此處我們考慮 單例 + @Autowired 的迴圈依賴,不考慮使用構造器注入原型作用域的Bean的注入。

2、代理物件何時建立

Bean建立的流程
注意:
正常情況下,即沒有發生 迴圈依賴的時候,aop增強是在 bean 初始化完成之後的 BeanPostProcessor#postProcessAfterInitialization方法中,但是如果有迴圈依賴發生的話,就需要提前,在 getEarlyBeanReference中提前建立代理物件。

3、3級快取中儲存的是什麼物件

快取欄位名 快取級別 資料型別 解釋
singletonObjects 1 Map<String, Object> 儲存的是完整的Bean,即可以使用的Bean
earlySingletonObjects 2 Map<String, Object> 儲存的是半成品的Bean,即屬性還沒有設定,沒有完成初始化工作
singletonFactories 3 Map<String, ObjectFactory<?>> 主要是生成Bean,然後放到二級快取中

注意:
ObjectFactory#getObject() 每呼叫一次,都會產生一個新的物件或返回舊物件,取決於是否存在代理等等。
ObjectFactory#getObject()

4、從3級快取中獲取物件

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

從3級快取中獲取物件

5 Spring Bean的簡化建立過程

1、例項化一個bean

Object bean = instanceWrapper.getWrappedInstance();

例項化Bean 即 new Bean()

2、加入到三級快取中

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

加入到三級快取中是有一些條件判斷的,一般都會是成立的,此處認為需要加入到三級快取。

3、設定bean的屬性

populateBean(beanName, mbd, instanceWrapper);

第一步例項化了bean,但是此時是沒有填充需要注入的屬性的,通過這一步進行屬性的填充。

4、初始化bean

Object exposedObject = initializeBean(beanName, exposedObject, mbd);

初始化Bean,執行初始化方法、Aware回撥、執行 BeanPostProcessor#postProcessAfterInitialization 方法 (aop的增強是在這個裡面實現的)

如果有迴圈引用的話,則aop的增強需要提前。

5、加入到一級快取中

addSingleton(......)

三、理解

@Component
class A {
    @Autowired
	private B b;
}

@Transaction (存在代理)
@Component
class B{
    @Autowired
	private A a;
}

1、假設只有singletonObjects和earlySingletonObjects可否完成迴圈依賴

快取欄位名 快取級別 資料型別 解釋
singletonObjects 1 Map<String, Object> 儲存的是完整的Bean,即可以使用的Bean
earlySingletonObjects 2 Map<String, Object> 儲存的是半成品的Bean,即屬性還沒有設定,沒有完成初始化工作

此時需要獲取 B的例項,即 getBean("b"),由上方瞭解到的 Bean 的簡化流程可知
SpringBean的簡化流程
獲取B的流程圖
由上圖可知,物件存在代理時,2級快取無法解決問題。因為代理物件是通過BeanPostProcessor來完成,是在設定屬性之後才產生的代理物件

此時可能有人會說,那如果我在構建完B的例項後,就立馬進行Aop代理,這樣不就解決問題了嗎?那假設A和B之間沒有發生迴圈依賴,這樣設計會不會不優雅?

2、假設只有singletonObjects和singletonFactories可否完成迴圈依賴

獲取B的流程圖
由圖中可知也是不可以實現的。

3、3級快取如何實現

1、解決代理問題

因為預設情況下,代理是通過BeanPostProcessor來完成,為了解決代理,就需要提前建立代理,那麼這個代理的建立就放到3級快取中來進行建立。

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

getEarlyBeanReference 此方法會返回代理bean

2、解決單例通過第3級快取多次獲取的值不一致

從3級快取中獲取物件
從上圖中可知,物件是先從 一級->二級->三級快取 這樣查詢,當三級快取產生了物件後就放入二級快取中快取起來,同時刪除三級快取。

3、流程圖

獲取B的流程圖

四、總結

1、一級快取 singletonObjects 存放可以使用的單例。
2、二級快取earlySingletonObjects存放的是早期的Bean,即是半成品,此時還是不可用的。
3、三級快取singletonFactories 是一個物件工廠,用於建立物件,然後放入到二級快取中。同時物件如果有Aop代理的話,這個對物件工廠返回的就是代理物件。

那可以在earlySingletonObjects中直接存放建立後的代理物件嗎?這樣是可以解決問題,但是設計可能就不合理了。因為在Spring中 Aop的代理是在物件完成之後建立的。而且如果沒有發生迴圈依賴的話,有必要提前建立代理物件嗎?分成三級快取,程式碼結構更清楚,更合理。

相關文章