Spring IoC 迴圈依賴的處理
阿新 • • 發佈:2020-06-27
# 前言
本系列全部基於 `Spring 5.2.2.BUILD-SNAPSHOT` 版本。因為 Spring 整個體系太過於龐大,所以只會進行關鍵部分的原始碼解析。
本篇文章主要介紹 Spring IoC 是怎麼解決迴圈依賴的問題的。
# 正文
## 什麼是迴圈依賴
迴圈依賴就是迴圈引用,就是兩個或多個 `bean` 相互之間的持有對方,比如A引用B,B引用A,像下面虛擬碼所示:
```java
public class A {
private B b;
// 省略get和set方法...
}
```
```java
public class B {
private A a;
// 省略get和set方法...
}
```
## Spring 如何解決迴圈依賴
Spring IoC 容器對迴圈依賴的處理有三種情況:
1. 構造器迴圈依賴:此依賴 Spring 無法處理,直接丟擲 `BeanCurrentlylnCreationException` 異常。
2. 單例作用域下的 `setter` 迴圈依賴:此依賴 Spring 通過三級快取來解決。
3. 非單例的迴圈依賴:此依賴 Spring 無法處理,直接丟擲 `BeanCurrentlylnCreationException` 異常。
### 構造器迴圈依賴
還是假設上面的A和B類是構造器迴圈依賴,如下所示:
```java
public class A {
private B b;
public A(B b) {
this.b = b;
}
// 省略get和set方法...
}
```
```java
public class B {
private A a;
public B(A a) {
this.a = a;
}
// 省略get和set方法...
}
```
然後我們在 XML 中配置了構造器自動注入,如下:
```xml
```
那麼我們在獲取 A 時,首先會進入 `doGetBean()` 方法(該方法在[Spring IoC bean 的載入](https://www.cnblogs.com/leisurexi/p/13194515.html)中分析過),會進行到如下程式碼塊:
```java
protected T doGetBean(final String name, @Nullable final Class requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
// 省略其它程式碼...
// 如果 bean 的作用域是單例
if (mbd.isSingleton()) {
// 建立和註冊單例 bean
sharedInstance = getSingleton(beanName, () -> {
try {
// 建立 bean 例項
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName);
throw ex;
}
});
// 獲取bean例項
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 省略其它程式碼...
}
```
上面方法中的 `getSingleton()` 方法會判斷是否是第一次建立該 `bean`,如果是第一次會先去建立 `bean`,也就是呼叫 `ObjectFacoty` 的 `getObject()` 方法,即呼叫 `createBean()` 方法建立 `bean` 前,會先將當前正要建立的 `bean` 記錄在快取 `singletonsCurrentlyInCreation` 中。
在建立A時發現依賴 B,便先去建立 B;B在建立時發現依賴A,此時A因為是通過建構函式建立,所以沒建立完,便又去建立A,發現A存在於 `singletonsCurrentlyInCreation`,即正在建立中,便丟擲 `BeanCurrentlylnCreationException` 異常。
```java
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);
// 一級快取中不存在當前 bean,也就是當前 bean 第一次建立
if (singletonObject == null) {
// 如果當前正在銷燬 singletons,丟擲異常
if (this.singletonsCurrentlyInDestruction) {
throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)");
}
// 建立單例 bean 之前的回撥
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
// 獲取 bean 例項
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
// 省略異常處理...
finally {
if (recordSuppressedExceptions) {
this.suppressedExceptions = null;
}
// 建立單例 bean 之後的回撥
afterSingletonCreation(beanName);
}
if (newSingleton) {
// 將 singletonObject 放入一級快取,並從二級和三級快取中移除
addSingleton(beanName, singletonObject);
}
}
// 返回 bean 例項
return singletonObject;
}
}
// 單例 bean 建立前的回撥方法,預設實現是將 beanName 加入到當前正在建立 bean 的快取中,
// 這樣便可以對迴圈依賴進行檢測
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
// 單例 bean 建立後的回撥方法,預設實現是將 beanName 從當前正在建立 bean 的快取中移除
protected void afterSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
// 這邊bean已經初始化完成了,放入一級快取
this.singletonObjects.put(beanName, singletonObject);
// 移除三級快取
this.singletonFactories.remove(beanName);
// 移除二級快取
this.earlySingletonObjects.remove(beanName);
// 將 beanName 新增到已註冊 bean 快取中
this.registeredSingletons.add(beanName);
}
}
```
### setter迴圈依賴
還是假設上面的A和B類是 field 屬性依賴注入迴圈依賴,如下所示:
```java
public class A {
private B b;
// 省略get和set方法...
}
```
```java
public class B {
private A a;
// 省略get和set方法...
}
```
然後我們在 XML 中配置了按照型別自動注入,如下:
```xml
```
Spring 在解決單例迴圈依賴時引入了三級快取,如下所示:
```java
// 一級快取,儲存已經初始化完成的bean
private final Map singletonObjects = new ConcurrentHashMap<>(256);
// 二級快取,儲存已經例項化完成的bean
private final Map earlySingletonObjects = new HashMap<>(16);
// 三級快取,儲存建立bean例項的ObjectFactory
private final Map> singletonFactories = new HashMap<>(16);
// 按先後順序記錄已經註冊的單例bean
private final Set registeredSingletons = new LinkedHashSet<>(256);
```
首先在建立A時,會進入到 `doCreateBean()` 方法(前面的流程可以檢視[Spring IoC bean 的建立](https://www.cnblogs.com/leisurexi/p/13196998.html)一文),如下:
```java
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
// 獲取bean的例項
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
// 通過建構函式反射建立bean的例項,但是屬性並未賦值
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
// 獲取bean的例項
final Object bean = instanceWrapper.getWrappedInstance();
// 省略其它程式碼...
// bean的作用域是單例 && 允許迴圈引用 && 當前bean正在建立中
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
// 如果允許bean提前曝光
if (earlySingletonExposure) {
// 將beanName和ObjectFactory形成的key-value對放入singletonFactories快取中
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 省略其它程式碼...
}
```
在呼叫 `addSingletonFactory()` 方法前A的例項已經創建出來了,只是還未進行屬性賦值和初始化階段,接下來將它放入了三級快取中,如下:
```java
protected void addSingletonFactory(String beanName, ObjectFactory> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
// 加鎖
synchronized (this.singletonObjects) {
// 如果一級快取中不包含當前bean
if (!this.singletonObjects.containsKey(beanName)) {
// 將ObjectFactory放入三級快取
this.singletonFactories.put(beanName, singletonFactory);
// 從二級快取中移除
this.earlySingletonObjects.remove(beanName);
// 將beanName加入到已經註冊過的單例bean快取中
this.registeredSingletons.add(beanName);
}
}
}
```
接下來A進行屬性賦值階段(會在後續文章中單獨分析這個階段),發現依賴B,便去獲取B,發現B還沒有被建立,所以走建立流程;在B進入屬性賦值階段時發現依賴A,就去呼叫 `getBean()` 方法獲取A,此時會進入 `getSingleton()` 方法(該方法的呼叫流程在[Spring IoC bean 的載入](https://www.cnblogs.com/leisurexi/p/13194515.html)一文中分析過),如下:
```java
public Object getSingleton(String beanName) {
// allowEarlyReference設定為true表示允許早期依賴
return getSingleton(beanName, true);
}
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);
// 二級快取為空 && bean允許提前曝光
if (singletonObject == null && allowEarlyReference) {
// 從三級快取中獲取bean對應的ObjectFactory
ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// 呼叫預先設定的getObject(),獲取bean例項
singletonObject = singletonFactory.getObject();
// 放入到二級快取中,並從三級快取中刪除
// 這時bean已經例項化完但還未初始化完
// 在該bean未初始化完時如果有別的bean引用該bean,可以直接從二級快取中取出返回
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
```
嘗試一級快取 `singletonObjects` (肯定沒有,因為A還沒初始化完全),嘗試二級快取 `earlySingletonObjects`(也沒有),嘗試三級快取 `singletonFactories`,由於A通過 `ObjectFactory` 將自己提前曝光了,所以B能夠通過 `ObjectFactory.getObject()` 拿到A物件(雖然A還沒有初始化完全,但是總比沒有好呀)。B拿到A後順利建立並初始化完成,呼叫上面分析過的 `addSingleton()` 方法將自己放入一級快取中。此時返回A中,A也能順利拿到完全初始化的B進行後續的階段,最後也將自己放入一級快取中,並從二級和三級快取中移除。
過程圖如下所示:
![](http://ww1.sinaimg.cn/large/006Vpl27ly1geq1d1g2x5j30qz0jddgu.jpg)
### 非單例迴圈依賴
對於非單例的 `bean`,Spring 容器無法完成依賴注入,因為 Spring 容器不進行快取,因此無法提前暴露一個建立中的 `bean`。
# 總結
本文主要介紹了 Spring 對三種迴圈依賴的處理,其實還有一種欄位迴圈依賴,比如 `@Autowired` 註解標註的欄位,但它和 `setter` 迴圈依賴的解決方法一樣,這裡就沒有多說。
> 最後,我模仿 Spring 寫了一個精簡版,程式碼會持續更新。地址:[https://github.com/leisurexi/tiny-spring](https://github.com/leisurexi/tiny-spring)。
# 參考
* 《Spring 原始碼深度解析》—— 郝佳
* https://juejin.im/post/5c98a7b4f265da60