前面系列3到系列7總共5篇文章分析了spring容器啟動的整個程序,但未對部分重要細節進行深入分析,比如spring回圈依賴,因此本節對spring回圈依賴進行深入分析,先思考以下四個問題:
A、 spring能解決所有的回圈依賴嗎?
B、 spring如何解決回圈依賴?
C、 一級快取以及二級快取能否解決回圈依賴?
D、為什么需要三級快取?
相信看完本文,上面問題豁然開朗,
1、預備知識
1.1、預備知識1——bean生命周期
回顧一下系列7中總結的bean生命周期,如下所示:

可以大致分成兩個主要階段:實體化和初始化,進而可以細分成:實體化前、實體化、實體化后、初始化前、初始化、初始化后,
1.2、預備知識2——依賴注入
依賴注入根據配置的不同,可以分成xml和注解兩種,如下:
-
xml
構造注入、setter注入、靜態工廠方法、實體工廠方法 -
注解
構造注入、filed注入構造注入——通過構造引數注入依賴
filed注入——@Autowired、@Resource
2、回圈依賴
2.1、什么是回圈依賴?

如上所示,InstantA依賴InstantB,InstantB依賴InstantC,依次往下傳遞到最終依賴InstantZ,InstantZ依賴InstantA,形成依賴倍訓,即回圈依賴,
2.2、構造注入
為了簡化分析難度,此處只分析A依賴B,B依賴A的情況,同時現在注解用得更廣泛,因此下面以注解作為demo進行分析,
@Component
public class InstantA {
private InstantB instantB;
public InstantA(InstantB instantB) {
this.instantB = instantB;
}
}
@Component
public class InstantB {
private InstantA instantA;
public InstantB(InstantA instantA) {
this.instantA = instantA;
}
}
可以看到InstantA中的屬性依賴InstantB,InstantB依賴InstantA,相互依賴,因此形成回圈依賴,
啟動spring容器時,報錯如下:

從啟動結果可以看出spring無法解決構造注入時產生的回圈依賴;
2.3、filed注入
@Component
public class InstantA {
@Autowired
private InstantB instantB;
}
@Component
public class InstantB {
@Autowired
private InstantA instantA;
}
容器能正常啟動并成功注入依賴,表明Spring能成功解決filed注入時產生的回圈依賴,
接下來,深入原始碼探究為什么不能解決構造注入時產生的回圈依賴,而可以解決filed注入時產生的回圈依賴?發車!
3、構造注入回圈依賴
3.1、原始碼分析
1. 從容器獲取insantA并將beanName加入singletonsCurrentlyInCreation集合
//正在創建的bean集合
/** Names of beans that are currently in creation. */
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
singletonsCurrentlyInCreation記錄當前正在創建bean的名字集合,
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//創建和銷毀沖突
if (this.singletonsCurrentlyInDestruction) {
//省略非關鍵代碼
}
if (logger.isDebugEnabled()) {
logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
}
//回呼前:將當前beanName加入singletonsCurrentlyInCreation,如果已在創建程序中則拋出例外
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
//執行回呼函式
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
//省略非關鍵代碼
}
catch (BeanCreationException ex) {
//省略非關鍵代碼
}
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
//回呼后:從singletonsCurrentlyInCreation移除beanName
afterSingletonCreation(beanName);
}
if (newSingleton) {
//從二級、三級快取移除,并將當前實體加入單例池中
addSingleton(beanName, singletonObject);
}
}
return singletonObject;
}
}
beforeSingletonCreation和afterSingletonCreation兩個方法分別將beanName從集合中加入或者移除,重點看beforeSingletonCreation
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
//當多次加入同一個beanName,則拋出例外 構造注入出現回圈依賴時 此處會拋出例外
throw new BeanCurrentlyInCreationException(beanName);
}
}
2. 推斷instantA的建構式并從容器獲取依賴instantB

因InstantA僅有一個帶參的建構式,因此只能用它,但發現需要InstantB作為構造引數,因此從容器中獲取InstantB;

3. 從容器獲取instantB將beanName加入singletonsCurrentlyInCreation

此時singletonsCurrentlyInCreation集合中有兩個beanName:instantA和instantB,
4.推斷實體化InstantB的建構式并從容器獲取依賴instantA


5. 再次從容器獲取instantA并將beanName加入singletonsCurrentlyInCreation拋出例外

當再次將instantA加入singletonsCurrentlyInCreation set集合時,此時集合中已經存在instantA,則拋出BeanCurrentlyInCreationException例外,InstantA和InstantB實體化時,都彼此需要對方作為引數,形成死回圈,將以上流程梳理總結如下,
3.2、流程總結

對上面流程簡短說明:
A、當實體InstantA前,將beanName instantA加入集合;
B、實體化InstantA時,發現依賴InstantB;
C、實體InstantB前,將beanName instantB加入集合;
D、實體化InstantB時,發現依賴InstantA;
E、從容器獲取InstantA,發現沒有,則再次執行創建流程,將beanName instantA加入集合,此時集合中已經存在beanName instantA,拋出例外;
因此,spring無法解決構造注入時產生的回圈依賴;
4、filed注入回圈依賴
4.1、 三級快取


4.2、原始碼分析
1. 從容器獲取instantA并將beanName instantA 加入singletonsCurrentlyInCreation

2. 推斷實體化instantA的建構式

3.將instanA加入三級快取

4. populateBean——填充instantA的屬性

為InstantA填充屬性instanB
5. 從容器獲取instantB并將beanName instantB加入singletonsCurrentlyInCreation

6. 推斷InstantB的建構式并生成實體

7. 將instantB加入三級快取

此時instantA和instantB都在三級快取中,
8. populateBean—填充instantB的屬性

經過決議@Autowired注解,發現instantB依賴InstantA,因此從容器中獲取InstantA.,

9. 再次從容器中獲取instantA——從三級快取中獲取instantA并加入二級快取

此時beanName instantA在singletonsCurrentlyInCreation集合中,因此193行方法isSingletonCurrentlyInCreation回傳true,此時繼續從二級三級快取查找,
此時三級快取中有instantA的回呼函式,執行回呼函式得到早期曝光bean并放入二級快取,同時將回呼函式從三級快取中移除,下面看如何執行三級回呼函式?
10. 執行instantA的三級快取回呼函式


生成cacheKey并放入earlyProxyReferences快取中;下面繼續看wrapIfNecessary方法:

此處instantA沒aop增強,所以上圖363行回傳的specificInterceptors為空,因此未創建代理物件,將原有的bean直接回傳,
11. 常規aop邏輯

常規創建aop代理物件是通過執行BeanPostProcessor的實作類AspectJAwareAdvisorAutoProxyCreator的postProcessAfterInitialization方法,
12. 填充instantB的屬性

將容器中獲取的instantA(此時位于二級快取)填充到instantB的屬性中,
13. 從singletonsCurrentlyInCreation移除instantB

移除后集合僅剩instantA,
14. 更新instantB的快取

當前instantB位于三級快取中,因此從三級快取中移除并將生成的bean放到一級快取單例池中;
15. 繼續回到創建instantA的populateBean程序中

將容器獲取的instantB填充到instantA實體中,
16. 更新instantA的快取
當instantA屬性填充完后,instantA生成完畢,此時進行快取更新,

當instantA屬性填充完后,再進行初始化,初始化后,instantA生成完畢并進行快取更新,此時instantA位于二級快取,將它從二級快取中移除并加入一級快取單例池,
4.3、流程總結

結合以上分析,可以看出spring可以解決filed注入產生的回圈依賴,主要借助以下四個集合(三級快取外加正在創建的bean集合):
//單例池
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//三級快取——存放剛實體化的bean
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//二級快取——存放早期曝光bean
/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//正在創建的bean集合
/** Names of beans that are currently in creation. */
private final Set<String> singletonsCurrentlyInCreation =Collections.newSetFromMap(new ConcurrentHashMap<>(16));
5、原型bean的回圈依賴
前面的回圈依賴主要是針對單例bean,接下來看看spring能否解決原型bean的回圈依賴?還是以注解手動注入為例,Go!
5.1、demo示例
@Component
@Scope("prototype")
public class InstantA {
@Autowired
private InstantB instantB;
}
@Component
@Scope("prototype")
public class InstantB {
@Autowired
private InstantA instantA;
}
5.2、原始碼分析
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) { //省略非關鍵代碼
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
//省略非關鍵代碼
if (!typeCheckOnly) {
markBeanAsCreated(beanName);
}
try {
// Create bean instance.
if (mbd.isSingleton()) {
//省略非關鍵代碼
}
else if (mbd.isPrototype()) {
// It's a prototype -> create a new instance.
Object prototypeInstance = null;
try {
//創建之前將beanName設定到執行緒中或者加入集合中
beforePrototypeCreation(beanName);//說明1
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
else {
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
public class NamedThreadLocal<T> extends ThreadLocal<T> {
private final String name;
/**
* Create a new NamedThreadLocal with the given name.
* @param name a descriptive name for this ThreadLocal
*/
public NamedThreadLocal(String name) {
Assert.hasText(name, "Name must not be empty");
this.name = name;
}
@Override
public String toString() {
return this.name;
}
}
- beforePrototypeCreation
protected void beforePrototypeCreation(String beanName) {
//如果ThreadLocal的value為空,則直接將beanName當作value
Object curVal = this.prototypesCurrentlyInCreation.get();
if (curVal == null) {
this.prototypesCurrentlyInCreation.set(beanName);
}
//如果ThreadLocal的value已經是String,則ThreadLocal的value需要保存多個值,則將新建hashSet作新value
else if (curVal instanceof String) {
Set<String> beanNameSet = new HashSet<>(2);
beanNameSet.add((String) curVal);
beanNameSet.add(beanName);
this.prototypesCurrentlyInCreation.set(beanNameSet);
}
else {
如果ThreadLocal的value已經是Set,則直接add
Set<String> beanNameSet = (Set<String>) curVal;
beanNameSet.add(beanName);
}
}
1. 實體化InstantA之前檢查prototypesCurrentlyInCreation是否包含instantA

2. 將instantA加入當前執行緒的prototypesCurrentlyInCreation

3. 填充InstantA實體,發現依賴InstantB

A、決議InstantA類的注解@Autowired發現依賴InstantB;
B、準備實體化InstantB,實體化之前檢查prototypesCurrentlyInCreation是否包含instantB;
C、此時不包含InstantB,將instantB加入prototypesCurrentlyInCreation;
經過以上步驟,prototypesCurrentlyInCreation集合中則有instantA和instantB兩個元素,如上圖所示;
4. 決議instantB發現需要依賴instantA,則再次從容器獲取instantA


獲取instantA之前再次檢查prototypesCurrentlyInCreation是否包含instantA,此時已經包含instantA,因此拋出例外,
5.3、總結
可以看到spring無法解決原型的filed注入回圈依賴,當然構造注入的回圈依賴也無法解決,
6、關于三級快取的思考
6.1、一級快取能否解決回圈依賴?
不行,
因為實體化后(未填充屬性)的bean以及完成屬性填充實體化的bean都放到一級快取,如果在實體化后與屬性填充之間獲取bean,則得到非完整bean,可能屬性為空;
6.2、二級快取能否解決回圈依賴?
A、如果沒aop,二級快取能解決;bean生產程序可以分成實體化和初始化兩個階段,實體化后放在二級快取,初始化完再放到一級快取,生成bean完成后,后續獲取bean只從一級快取中獲取,可以保證bean的完整性;
B、如果有aop,二級無法解決回圈依賴,會出現二級快取中相同的beanName在不同階段(實體化后和初始化后)不是同一個bean(因此執行aop后會回傳代理物件,和之前的bean不是同一個物件),導致混亂;
6.3、三級快取存在的意義
A、為什么不直接在放入兩級快取之前提前執行aop邏輯?
因為回圈依賴的相對較少,沒必要針對小部分實體執行一遍(wrapIfNecessary),反正后續的BeanPostProcessor中的postProcessAfterInitialization有對aop的處理;
B、三級快取的目的就是讓如果有回圈依賴,提前執行可能存在的aop操作(如果存在aop),從而放入二級快取的bean是生成的代理物件,從而保證二級快取中相同的beanName是同一個bean;
7、總結
還記得開始前的四個問題嗎?
-
spring能解決所有的回圈依賴嗎?
答:
原型bean:spring不能解決原型bean任何注入方式產生的回圈依賴;
單例bean:能解決單例bean在setter、filed注入時產生的回圈依賴,不能解決構造注入時產生的回圈依賴; -
spring如何解決回圈依賴?
答:三級快取+singletonsCurrentlyInCreation(正在創建的bean集合)
-
一級快取以及二級快取能否解決回圈依賴?
答:不能; -
為什么需要三級快取?
答:相信從三級快取思考中得到答案;
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/299647.html
標籤:其他
