1. 程式人生 > 實用技巧 >Spring IoC 迴圈依賴的處理

Spring IoC 迴圈依賴的處理

前言

本系列全部基於 Spring 5.2.2.BUILD-SNAPSHOT 版本。因為 Spring 整個體系太過於龐大,所以只會進行關鍵部分的原始碼解析。

本篇文章主要介紹 Spring IoC 是怎麼解決迴圈依賴的問題的。

正文

什麼是迴圈依賴

迴圈依賴就是迴圈引用,就是兩個或多個 bean 相互之間的持有對方,比如A引用B,B引用A,像下面虛擬碼所示:

public class A {
private B b; // 省略get和set方法...
}
public class B {
private A a; // 省略get和set方法...
}

Spring 如何解決迴圈依賴

Spring IoC 容器對迴圈依賴的處理有三種情況:

  1. 構造器迴圈依賴:此依賴 Spring 無法處理,直接丟擲 BeanCurrentlylnCreationException 異常。
  2. 單例作用域下的 setter 迴圈依賴:此依賴 Spring 通過三級快取來解決。
  3. 非單例的迴圈依賴:此依賴 Spring 無法處理,直接丟擲 BeanCurrentlylnCreationException 異常。

構造器迴圈依賴

還是假設上面的A和B類是構造器迴圈依賴,如下所示:

public class A {
private B b; public A(B b) {
this.b = b;
} // 省略get和set方法...
}
public class B {
private A a; public B(A a) {
this.a = a;
} // 省略get和set方法...
}

然後我們在 XML 中配置了構造器自動注入,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="a" class="com.leisurexi.ioc.circular.reference.A" autowire="constructor" /> <bean id="b" class="com.leisurexi.ioc.circular.reference.B" autowire="constructor" /> </beans>

那麼我們在獲取 A 時,首先會進入 doGetBean() 方法(該方法在Spring IoC bean 的載入中分析過),會進行到如下程式碼塊:

protected <T> T doGetBean(final String name, @Nullable final Class<T> 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,也就是呼叫 ObjectFacotygetObject() 方法,即呼叫 createBean() 方法建立 bean 前,會先將當前正要建立的 bean 記錄在快取 singletonsCurrentlyInCreation 中。

在建立A時發現依賴 B,便先去建立 B;B在建立時發現依賴A,此時A因為是通過建構函式建立,所以沒建立完,便又去建立A,發現A存在於 singletonsCurrentlyInCreation,即正在建立中,便丟擲 BeanCurrentlylnCreationException 異常。

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 屬性依賴注入迴圈依賴,如下所示:

public class A {
private B b; // 省略get和set方法...
}
public class B {
private A a; // 省略get和set方法...
}

然後我們在 XML 中配置了按照型別自動注入,如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="a" class="com.leisurexi.ioc.circular.reference.A" autowire="byType" /> <bean id="b" class="com.leisurexi.ioc.circular.reference.B" autowire="byType" /> </beans>

Spring 在解決單例迴圈依賴時引入了三級快取,如下所示:

// 一級快取,儲存已經初始化完成的bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // 二級快取,儲存已經例項化完成的bean
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16); // 三級快取,儲存建立bean例項的ObjectFactory
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16); // 按先後順序記錄已經註冊的單例bean
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);

首先在建立A時,會進入到 doCreateBean() 方法(前面的流程可以檢視Spring IoC bean 的建立一文),如下:

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的例項已經創建出來了,只是還未進行屬性賦值和初始化階段,接下來將它放入了三級快取中,如下:

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 的載入一文中分析過),如下:

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進行後續的階段,最後也將自己放入一級快取中,並從二級和三級快取中移除。

過程圖如下所示:

非單例迴圈依賴

對於非單例的 bean,Spring 容器無法完成依賴注入,因為 Spring 容器不進行快取,因此無法提前暴露一個建立中的 bean

總結

本文主要介紹了 Spring 對三種迴圈依賴的處理,其實還有一種欄位迴圈依賴,比如 @Autowired 註解標註的欄位,但它和 setter 迴圈依賴的解決方法一樣,這裡就沒有多說。

最後,我模仿 Spring 寫了一個精簡版,程式碼會持續更新。地址:https://github.com/leisurexi/tiny-spring

參考