前言
你是不是被這個騷氣的標題吸引進來的,_ 喜歡我的文章的話就給個好評吧,你的肯定是我堅持寫作最大的動力,來吧兄弟們,給我一點動力
Spring如何處理回圈依賴?這是最近較為頻繁被問到的一個面試題,在前面Bean實體化流程中,對屬性注入一文多多少少對回圈依賴有過介紹,這篇文章詳細講一下Spring中的回圈依賴的處理方案,
什么是回圈依賴
依賴指的是Bean與Bean之間的依賴關系,回圈依賴指的是兩個或者多個Bean相互依賴,如:

構造器回圈依賴
代碼示例:
public class BeanA {
private BeanB beanB;
public BeanA(BeanB beanB){
this.beanB = beanB;
}
}
public class BeanB {
private BeanA beanA;
public BeanB(BeanA beanA){
this.beanA = beanA;
}
}
組態檔
<bean id="beanA" class="cn.itsource._01_di.BeanA" >
<constructor-arg type="cn.itsource._01_di.BeanB" ref="beanB" />
</bean>
<bean id="beanB" class="cn.itsource._01_di.BeanB" >
<constructor-arg type="cn.itsource._01_di.BeanA" ref="beanA" />
</bean>
Setter回圈依賴
代碼示例
public class BeanA {
private BeanB beanB;
public void setBeanB(BeanB beanB){
this.beanB = beanB;
}
}
@Data
public class BeanB {
private BeanA beanA;
public void setBeanA(BeanA beanA){
this.beanA = beanA;
}
}
組態檔
<bean id="beanA" class="cn.itsource._01_di.BeanA" >
<property name="beanB" ref="beanB" />
</bean>
<bean id="beanB" class="cn.itsource._01_di.BeanB">
<property name="beanA" ref="beanA" />
</bean>
回圈依賴包括: 構造器注入回圈依賴 set , 注入回圈依賴 和 prototype模式Bean的回圈依賴,Spring只解決了單利Bean的 setter 注入回圈依賴,對于構造器回圈依賴,和 prototype模式的回圈依賴是無法解決的,在創建Bean的時候就會拋出例外 :“BeanCurrentlyInCreationException” ,
回圈依賴控制開關在 AbstractRefreshableApplicationContext 容器工廠類中有定義:
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
@Nullable
private Boolean allowBeanDefinitionOverriding;
//是否允許回圈依賴
@Nullable
private Boolean allowCircularReferences;
//設定回圈依賴
public void setAllowCircularReferences(boolean allowCircularReferences) {
this.allowCircularReferences = allowCircularReferences;
}
默認情況下是允許Bean之間的回圈依賴的,在依賴注入時Spring會嘗試處理回圈依賴,如果將該屬性配置為“false”則關倍訓圈依賴,當在Bean依賴注入的時遇到回圈依賴時拋出例外,可以通過如下方式關閉,但是一般都不這么做
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//禁用回圈依賴
applicationContext.setAllowCircularReferences(false);
//重繪容器
applicationContext.refresh();
...
構造器回圈依賴處理
構造器是不允許回圈依賴的,動動你的小腦瓜想一想,比如:A 依賴 B ,B依賴C,C依賴A,在實體化A的時候,構造器需要注入B,然后Spirng會實體化B,此時的A屬于“正在創建”的狀態,當實體化B的時候,發現構造器需要注入C,然后去實體化C,然而實體化C的時候又需要注入A的實體,這樣就造成了一個死回圈,永遠無法先實體化出某一個Bean,所以Spring遇到這里構造器回圈依賴會直接拋出例外,
那么Spring到底是如何做的呢?
-
首先Spring會走Bean的實體化流程嘗試創建 A 的實體 ,在創建實體之間先從 “正在創建Bean池” (一個快取Map而已)中去查找A 是否正在創建,如果沒找到,則將 A 放入 “正在創建Bean池”中,然后準備實體化構造器引數 B,
-
Spring會走Bean的實體化流程嘗試創建 B 的實體 ,在創建實體之間先從 “正在創建Bean池” (一個快取Map而已)中去查找B 是否正在創建,如果沒找到,則將 B 放入 “正在創建Bean池”中,然后準備實體化構造器引數 A,
-
Spring會走Bean的實體化流程嘗試創建 A 的實體 ,在創建實體之間先從 “正在創建Bean池” (一個快取Map而已)中去查找A 是否正在創建,
-
此時:Spring發現 A 正處于“正在創建Bean池”,表示出現構造器回圈依賴,拋出例外:“BeanCurrentlyInCreationException”
DefaultSingletonBeanRegistry#getSingleton
下面我們以 BeanA 構造引數依賴BeanB, BeanB 構造引數依賴BeanA 為例來分析,
當Spring的IOC容器啟動,嘗試對單利的BeanA進行初始化,根據之前的分析我們知道,單利Bean的創建入口是 AbstractBeanFactory#doGetBean 在該方法中會先從單利Bean快取中獲取,如果沒有代碼會走到:DefaultSingletonBeanRegistry#getSingleton(jString beanName, ObjectFactory<?> singletonFactory) 方法中 ,在該方法中會先對把創建的Bean加入 一個名字為 singletonsCurrentlyInCreation 的 ConcurrentHashMap中,意思是該Bean正在創建中,然后呼叫 ObjectFactory.getObject() 實體化Bean , 假設 BeanA 進入了該方法進行實體化:
//正在創建中的Bean
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<>(16));
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
...省略...
//把該Bean的名字加入 singletonsCurrentlyInCreation 正在創建池 中
beforeSingletonCreation(beanName);
boolean newSingleton = false;
boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
if (recordSuppressedExceptions) {
this.suppressedExceptions = new LinkedHashSet<>();
}
try {
//呼叫ObjectFactory創建Bean的實體
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
...省略...
//如果singletonsCurrentlyInCreation中沒該Bean,就把該Bean存盤到singletonsCurrentlyInCreation中,
//如果 singletonsCurrentlyInCreation 中有 該Bean,就報錯回圈依賴例外BeanCurrentlyInCreationException
//也就意味著同一個beanName進入該方法2次就會拋例外
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
beforeSingletonCreation 方法非常關鍵 ,它會把beanName加入 singletonsCurrentlyInCreation,一個代表“正在創建中的Bean”的ConcurrentHashMap中,
- 如果singletonsCurrentlyInCreation中沒該beanName,就把該Bean存盤到singletonsCurrentlyInCreation中,
- 如果 singletonsCurrentlyInCreation 中有 該Bean,就報錯回圈依賴例外BeanCurrentlyInCreationException
【注意】也就意味著同一個beanName進入該方法2次就會拋例外 , 現在BeanA已經加入了singletonsCurrentlyInCreation
AbstractAutowireCapableBeanFactory#autowireConstructor
我們前面分析過 ObjectFactory.getObject實體化Bean的詳細流程,這里我只是大概在復盤一下就行了,因為我們的BeanA的構造器注入了一個BeanB,所以 代碼最侄訓走到AbstractAutowireCapableBeanFactory#autowireConstructor ,通過構造器來實體化BeanA(在屬性注入那一章有講到 ) ,
在autowireConstructor 方法中會通過 ConstructorResolver#resolveConstructorArguments 來決議構造引數,呼叫 BeanDefinitionValueResolver 去把 ref="beanB" 這種字串的參考變成一個實實在在的Bean,即BeanB,所以在 BeanDefinitionValueResolver 屬性值決議器中又會去實體化BeanB,同樣會走到 DefaultSingletonBeanRegistry#getSingleton 中把BeanB加入 singletonsCurrentlyInCreation “正在創建Bean池”中,然后呼叫ObjectFactory.getObject實體化BeanB,
低于BeanB而已同樣需要通過構造器創建,BeanB構造器引數依賴了BeanA,也就意味著又會呼叫 BeanDefinitionValueResolver 去把 ref=“beanA” 這種字串參考變成容器中的BeanA的Bean實體,然后代碼又會走到 DefaultSingletonBeanRegistry#getSingleton,然后再一次的嘗試把BeanA加入singletonsCurrentlyInCreation “正在創建Bean池”,
此時問題就來了,在最開始創建BeanA的時候它已經加入過一次“正在創建Bean” 池,這會兒實體化BeanB的時候,由于構造器引數依賴了BeanA,導致BeanA又想進入“正在創建Bean” 池 ,此時 Spring拋出回圈依賴例外:
Error creating bean with name ‘beanA’: Requested bean is currently in creation: Is there an unresolvable circular reference?
到這,Spring處理構造器回圈依賴的原始碼分析完畢,
setter回圈依賴處理
setter回圈依賴是可以允許的,Spring是通過提前暴露未實體化完成的Bean的 ObjectFactory 來實作回圈依賴的,這樣做的目的是其他的Bean可以通過 ObjectFactory 參考到該Bean,
實作流程如下:
- Spring創建BeanA,通過無參構造實體化,并暴露一個ObjectFactory,用來獲取創建中的BeanA,然后把BeanA添加到“正在創建Bean池”中,然后通過setter注入BeanB
- Spring創建BeanB,通過無參構造實體化,并暴露一個ObjectFactory,用來獲取創建中的BeanB,然后把BeanB添加到“正在創建Bean池”中,然后通過setter注入BeanA
- 在BeanB通過setter注入BeanA時,由于BeanA 提前暴露了ObjectFactory ,通過它回傳一個提前暴露一個創建中的BeanA,
- 然后完成BeanB的依賴注入
AbstractAutowireCapableBeanFactory#doCreateBean
我們以BeanA 通過settter依賴BeanB,BeanB通過setter 依賴BeanA為例來分析一下原始碼,在之前的Bean實體化流程分析程序中我們了解到,Bean的實體化會走AbstractBeanFactory#doGetBean,然后查找單利快取中是否有該Bean ,如果沒有就呼叫 DefaultSingletonBeanRegistry#getSingleton,方法會把BeanA加入 singletonsCurrentlyInCreation “創建中的Bean池”,然后呼叫ObjectFactory.getObject創建Bean.
org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean 原始碼:
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
//快取中獲取Bean,解決了回圈依賴問題
Object sharedInstance = getSingleton(beanName);
...快取中沒有走下面...
if (mbd.isSingleton()) {
//走 DefaultSingletonBeanRegistry#getSingleton ,方法會把bean加入“正在創建bean池”
//然后呼叫ObjectFactory實體化Bean
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
第一次進來,快取中是沒有BeanA的,所有會走 getSingleton 方法,然后代碼最侄訓走到AbstractAutowireCapableBeanFactory#doCreateBean 方法中 ,
AbstractAutowireCapableBeanFactory#doCreateBean原始碼:
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// Instantiate the bean.
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
//實體化Bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
}
...省略...
//如果是單利 ,如果是允許回圈依賴,如果 beanName 出于創建中,已經被添加到“創建中的bean池”
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//把ObjectFactory 添加到 singletonFactories 中,
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
try {
//走依賴注入流程
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
//快取單利Bean的創建工廠,用于解決回圈依賴
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
//singletonObjects單利快取中是否包含Bean
if (!this.singletonObjects.containsKey(beanName)) {
//提前暴露ObjectFactory,把ObjectFactory放到singletonFactories中,
//后面解決回圈依賴,獲取Bean實體的時候會用到
this.singletonFactories.put(beanName, singletonFactory);
//早期單利bean快取中移除Bean
this.earlySingletonObjects.remove(beanName);
//把注冊的Bean加入registeredSingletons中
this.registeredSingletons.add(beanName);
}
}
}
該方法中把BeanA實體化好之后,會把ObjectFactory存盤到一個 singletonFactories (HashMap)中來提前暴露Bean的創建工廠,用于解決回圈依賴【重要】,然后呼叫 populateBean 走屬性注入流程,
屬性注入會通過BeanDefinition得到bean的依賴屬性,然后呼叫 AbstractAutowireCapableBeanFactory#applyPropertyValues ,把屬性應用到物件上,在applyPropertyValues 方法中最終呼叫 BeanDefinitionValueResolver#resolveValueIfNecessary 決議屬性值,比如:ref=“beanB” 這種字串參考變成 物件實體的參考,
在BeanDefinitionValueResolver決議依賴的屬性值即:BeanB的時候,同樣會觸發BeanB的實體化,代碼會走到AbstractBeanFactory#doGetBean ,然后走方法 DefaultSingletonBeanRegistry#getSingleton 中把BeanB加入 singletonsCurrentlyInCreation “創建中的Bean池”,然后代碼會走到AbstractAutowireCapableBeanFactory#doCreateBean 方法中創建BeanB,
該方法中會先實體化BeanB,接著會把BeanB的ObjectFactory存盤到 singletonFactories (HashMap)中來提前暴露Bean的創建工廠,用于解決回圈依賴,然后呼叫 populateBean 走屬性注入流程,
同樣因為BeanB通過Setter 注入了 A,所以在 populateBean 屬性注入流程中會決議 ref=“beanA” 為容器中的 BeanA 的實體,
然后會走到 AbstractBeanFactory#doGetBean 中獲取BeanA的實體,這個時候流程就不一樣了,我們先看一下 AbstractBeanFactory#doGetBean 中的代碼
protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// Eagerly check singleton cache for manually registered singletons.
//從快取中獲取Bean
Object sharedInstance = getSingleton(beanName);
...省略...
//如果快取中沒有Bean,就創建Bean
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// Explicitly remove instance from singleton cache: It might have been put there
// eagerly by the creation process, to allow for circular reference resolution.
// Also remove any beans that received a temporary reference to the bean.
destroySingleton(beanName);
throw ex;
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
在獲取單利Bean的實體的時候是會先去單利Bean的快取中去查看Bean是否已經存在,如果不存在,才會走DefaultSingletonBeanRegistry#getSingleton方法創建Bean,
問題是:此刻單利Bean快取中已經有BeanA了,因為在最開始BeanA已經出于“正在創建Bean池”中了,我們先來看一下是如何從快取獲取Bean的,
DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)原始碼如下:
//allowEarlyReference :是否創建早期應用,主要用來解決回圈依賴
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
//從Map中 singletonObjects = new ConcurrentHashMap<>(256); 獲取單利Bean
//【一級快取】singletonObject快取中是否有Bean , 它存盤的是已經實體化好的Bean
Object singletonObject = this.singletonObjects.get(beanName);
//如果singletonObjects中沒有Bean,但是Bean出于正在創建池中,即: Set<String> singletonsCurrentlyInCreation中有Bean,
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
//【二級快取】從早期單例物件的快取 earlySingletonObjects 中獲取
singletonObject = this.earlySingletonObjects.get(beanName);
//早期單利物件快取中也沒有,但是允許回圈依賴
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
//【三級快取】獲取ObjectFactory , 物件創建工廠,得到Bean創建程序中提前暴露的工廠,
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//通過工廠ObjectFactory 獲取物件實體
singletonObject = singletonFactory.getObject();
//把物件存盤到早期快取中
this.earlySingletonObjects.put(beanName, singletonObject);
//把ObjectFactory移除
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
這里就是經典的三級快取解決Spring回圈依賴,你看到了,這里會先從 singletonObjects 單利Bean快取集合中獲取Bean(該快取是實體化完成了的Bean),如果沒有,就從earlySingletonObjects早期物件快取中獲取Bean(該快取中存放的是還未實體化完成的早期Bean),如果還是沒有,就從singletonFactories中得到暴露的ObjectFactory來獲取依賴的Bean,然后放入早期快取中,并把ObjectFactory從singletonFactories中移除,最后回傳Bean的實體,
由于在實體化BeanA的時候已經把BeanA的ObjectFactory添加到了 singletonFactories 快取中,那么這里就會走到 singletonFactory.getObject(); 方法得到BeanA的實體,并且會把BeanA存盤到 earlySingletonObjects早期單利Bean快取中,
BeanA的實體成功回傳,那么BeanB的 setter注入成功,代表BeanB實體化完成,那么BeanA的setter方法注入成功,BeanA實體化完成,
prototype模式的回圈依賴
對于prototype模式下的Bean不允許回圈依賴,因為 這種模式下Bean是不做快取的,所以就沒法暴露ObjectFactory,也就沒辦法實作回圈依賴,
總結
不知道你有沒有看暈,反正我但是在原始碼時的程序是比較辛苦的~~~~(>_<)~~~~ ,這里需要你對前面Bean的實體化流程和屬性注入流程比較熟悉,否則就會暈菜,
這里總結一下:
- 構造器回圈依賴是不允許的,主要通過 singletonsCurrentlyInCreation “正在創建Bean池” 把創建中的Bean快取起來,如果回圈依賴,同一個Bean勢必會嘗試進入該快取2次,拋出回圈依賴例外,
- setter回圈依賴是可以允許的,Spring是通過提前暴露未實體化完成的Bean的 ObjectFactory 來實作回圈依賴的,這樣做的目的是其他的Bean可以通過 ObjectFactory 參考到該Bean , 在獲取依賴的Bean的時候使用到了三級快取,
下面的面試題你會答了嗎?
- Spirng支持那種模式下的回圈依賴(構造器?,setter?, prototype?)
- Spring是如何處理構造器注入回圈依賴的?
- Spring是如何處理Setter注入回圈依賴的?
喜歡我的文章的話就給個好評吧,你的肯定是我堅持寫作最大的動力,來吧兄弟們,給我一點動力
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/287377.html
標籤:java
上一篇:Java基礎面試題(建議收藏)
下一篇:揭秘游戲服務器,不看后悔!!!
