關(guān)于spring循環(huán)依賴問題及解決方案
一、三種循環(huán)依賴的情況
①構(gòu)造器的循環(huán)依賴:
- 這種依賴spring是處理不了的,直接拋出BeanCurrentlylnCreationException異常。
②單例模式下的setter循環(huán)依賴:
- 通過“三級緩存”處理循環(huán)依賴,能處理。
③非單例循環(huán)依賴:
- 無法處理。原型(Prototype)的場景是不支持循環(huán)依賴的,通常會走到AbstractBeanFactory類中下面的判斷,拋出異常。
if (isPrototypeCurrentlyInCreation(beanName))?
{ ?throw new BeanCurrentlyInCreationException(beanName);}原因很好理解,創(chuàng)建新的A時,發(fā)現(xiàn)要注入原型字段B,又創(chuàng)建新的B發(fā)現(xiàn)要注入原型字段A…這就套娃了, 你猜是先StackOverflow還是OutOfMemory?
Spring怕你不好猜,就先拋出了BeanCurrentlyInCreationException
出現(xiàn)的背景:
比如幾個Bean之間的互相引用

甚至自己“循環(huán)”依賴自己

二、解決方案
首先,Spring內(nèi)部維護了三個Map,也就是我們通常說的三級緩存。
筆者翻閱Spring文檔倒是沒有找到三級緩存的概念,可能也是本土為了方便理解的詞匯。
在Spring的DefaultSingletonBeanRegistry類中,你會赫然發(fā)現(xiàn)類上方掛著這三個Map:
singletonObjects(一級緩存)它是我們最熟悉的朋友,俗稱“單例池”“容器”,緩存創(chuàng)建完成單例Bean的地方。earlySingletonObjects(二級緩存)映射Bean的早期引用,也就是說在這個Map里的Bean不是完整的,甚至還不能稱之為“Bean”,只是一個Instance.singletonFactories(三級緩存) 映射創(chuàng)建Bean的原始工廠

后兩個Map其實是“墊腳石”級別的,只是創(chuàng)建Bean的時候,用來借助了一下,創(chuàng)建完成就清掉了。
那么Spring 是如何通過上面介紹的三級緩存來解決循環(huán)依賴的呢?
這里只用 A,B 形成的循環(huán)依賴來舉例:
- 實例化 A,此時 A 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行,A 只是一個半成品。
- 為 A 創(chuàng)建一個 Bean工廠,并放入到 singletonFactories 中。
- 發(fā)現(xiàn) A 需要注入 B 對象,但是一級、二級、三級緩存均為發(fā)現(xiàn)對象 B。
- 實例化 B,此時 B 還未完成屬性填充和初始化方法(@PostConstruct)的執(zhí)行,B 只是一個半成品。
- 為 B 創(chuàng)建一個 Bean工廠,并放入到 singletonFactories 中。
- 發(fā)現(xiàn) B 需要注入 A 對象,此時在一級、二級未發(fā)現(xiàn)對象A,但是在三級緩存中發(fā)現(xiàn)了對象 A,從三級緩存中得到對象 A,并將對象 A 放入二級緩存中,同時刪除三級緩存中的對象 A。(注意,此時的 A還是一個半成品,并沒有完成屬性填充和執(zhí)行初始化方法)
- 將對象 A 注入到對象 B 中。
- 對象 B 完成屬性填充,執(zhí)行初始化方法,并放入到一級緩存中,同時刪除二級緩存中的對象 B。(此時對象 B 已經(jīng)是一個成品)
- 對象 A 得到對象B,將對象 B 注入到對象 A 中。(對象 A 得到的是一個完整的對象 B)
- 對象 A完成屬性填充,執(zhí)行初始化方法,并放入到一級緩存中,同時刪除二級緩存中的對象 A。

我們從源碼的角度來看一下這個過程:
創(chuàng)建 Bean 的方法在 AbstractAutowireCapableBeanFactory::doCreateBean()
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
BeanWrapper instanceWrapper = null;
if (instanceWrapper == null) {
// ① 實例化對象
instanceWrapper = this.createBeanInstance(beanName, mbd, args);
}
final Object bean = instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null;
Class<?> beanType = instanceWrapper != null ? instanceWrapper.getWrappedClass() : null;
// ② 判斷是否允許提前暴露對象,如果允許,則直接添加一個 ObjectFactory 到三級緩存
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
// 添加三級緩存的方法詳情在下方
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// ③ 填充屬性
this.populateBean(beanName, mbd, instanceWrapper);
// ④ 執(zhí)行初始化方法,并創(chuàng)建代理
exposedObject = initializeBean(beanName, exposedObject, mbd);
return exposedObject;
}
添加三級緩存的方法如下:
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) { // 判斷一級緩存中不存在此對象
this.singletonFactories.put(beanName, singletonFactory); // 添加至三級緩存
this.earlySingletonObjects.remove(beanName); // 確保二級緩存沒有此對象
this.registeredSingletons.add(beanName);
}
}
}
@FunctionalInterface
public interface ObjectFactory<T> {
T getObject() throws BeansException;
}
通過這段代碼,我們可以知道 Spring 在實例化對象的之后,就會為其創(chuàng)建一個 Bean 工廠,并將此工廠加入到三級緩存中。
因此,Spring 一開始提前暴露的并不是實例化的 Bean,而是將 Bean 包裝起來的 ObjectFactory。為什么要這么做呢?
這實際上涉及到 AOP,如果創(chuàng)建的 Bean 是有代理的,那么注入的就應(yīng)該是代理 Bean,而不是原始的 Bean。但是 Spring 一開始并不知道 Bean 是否會有循環(huán)依賴,通常情況下(沒有循環(huán)依賴的情況下),Spring 都會在完成填充屬性,并且執(zhí)行完初始化方法之后再為其創(chuàng)建代理。但是,如果出現(xiàn)了循環(huán)依賴的話,Spring 就不得不為其提前創(chuàng)建代理對象,否則注入的就是一個原始對象,而不是代理對象。因此,這里就涉及到應(yīng)該在哪里提前創(chuàng)建代理對象?
Spring 的做法就是在 ObjectFactory 中去提前創(chuàng)建代理對象。它會執(zhí)行 getObject() 方法來獲取到 Bean。實際上,它真正執(zhí)行的方法如下:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
// 如果需要代理,這里會返回代理對象;否則返回原始對象
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
因為提前進行了代理,避免對后面重復(fù)創(chuàng)建代理對象,會在 earlyProxyReferences 中記錄已被代理的對象。
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
// 記錄已被代理的對象
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
通過上面的解析,我們可以知道 Spring 需要三級緩存的目的是為了在沒有循環(huán)依賴的情況下,延遲代理對象的創(chuàng)建,使 Bean 的創(chuàng)建符合 Spring 的設(shè)計原則。
如何獲取依賴
我們目前已經(jīng)知道了 Spring 的三級依賴的作用,但是 Spring 在注入屬性的時候是如何去獲取依賴的呢?
他是通過一個getSingleton()方法去獲取所需要的 Bean 的。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 一級緩存
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 二級緩存
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
// 三級緩存
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
// Bean 工廠中獲取 Bean
singletonObject = singletonFactory.getObject();
// 放入到二級緩存中
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
當 Spring 為某個 Bean 填充屬性的時候,它首先會尋找需要注入對象的名稱,然后依次執(zhí)行 getSingleton() 方法得到所需注入的對象,而獲取對象的過程就是先從一級緩存中獲取,一級緩存中沒有就從二級緩存中獲取,二級緩存中沒有就從三級緩存中獲取,如果三級緩存中也沒有,那么就會去執(zhí)行 doCreateBean() 方法創(chuàng)建這個 Bean。
流程圖總結(jié):

三、解決循環(huán)依賴必須要三級緩存嗎
我們現(xiàn)在已經(jīng)知道,第三級緩存的目的是為了延遲代理對象的創(chuàng)建,因為如果沒有依賴循環(huán)的話,那么就不需要為其提前創(chuàng)建代理,可以將它延遲到初始化完成之后再創(chuàng)建。
既然目的只是延遲的話,那么我們是不是可以不延遲創(chuàng)建,而是在實例化完成之后,就為其創(chuàng)建代理對象,這樣我們就不需要第三級緩存了。因此,我們可以將addSingletonFactory() 方法進行改造。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(singletonFactory, "Singleton factory must not be null");
synchronized (this.singletonObjects) {
if (!this.singletonObjects.containsKey(beanName)) { // 判斷一級緩存中不存在此對象
object o = singletonFactory.getObject(); // 直接從工廠中獲取 Bean
this.earlySingletonObjects.put(beanName, o); // 添加至二級緩存中
this.registeredSingletons.add(beanName);
}
}
}
這樣的話,每次實例化完 Bean 之后就直接去創(chuàng)建代理對象,并添加到二級緩存中。測試結(jié)果是完全正常的,Spring 的初始化時間應(yīng)該也是不會有太大的影響,因為如果 Bean 本身不需要代理的話,是直接返回原始 Bean 的,并不需要走復(fù)雜的創(chuàng)建代理 Bean 的流程。
結(jié)論
測試證明,二級緩存也是可以解決循環(huán)依賴的。為什么 Spring 不選擇二級緩存,而要額外多添加一層緩存呢?
如果 Spring 選擇二級緩存來解決循環(huán)依賴的話,那么就意味著所有 Bean 都需要在實例化完成之后就立馬為其創(chuàng)建代理,而Spring 的設(shè)計原則是在 Bean 初始化完成之后才為其創(chuàng)建代理。所以,Spring 選擇了三級緩存。但是因為循環(huán)依賴的出現(xiàn),導(dǎo)致了 Spring 不得不提前去創(chuàng)建代理,因為如果不提前創(chuàng)建代理對象,那么注入的就是原始對象,這樣就會產(chǎn)生錯誤。
四、無法解決的循環(huán)依賴問題
1.在主bean中通過構(gòu)造函數(shù)注入所依賴的bean
如下controller為主bean,service為所依賴的bean:
@RestController
public class AccountController {
private static final Logger LOG = LoggerFactory.getLogger(AccountController.class);
private AccountService accountService;
// 構(gòu)造函數(shù)依賴注入
// 不管是否設(shè)置為required為true,都會出現(xiàn)循環(huán)依賴問題
@Autowire
// @Autowired(required = false)
public AccountController(AccountService accountService) {
this.accountService = accountService;
}
}
@Service
public class AccountService {
private static final Logger LOG = LoggerFactory.getLogger(AccountService.class);
// 屬性值依賴注入
@Autowired
private AccountController accountController;
}
啟動打印如下:
***************************
APPLICATION FAILED TO START
***************************Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| accountController defined in file [/Users/xieyizun/study/personal-projects/easy-web/target/classes/com/yzxie/easy/log/web/controller/AccountController.class]
↑ ↓
| accountService (field private com.yzxie.easy.log.web.controller.AccountController com.yzxie.easy.log.web.service.AccountService.accountController)
└─────┘
如果是在主bean中通過屬性值或者setter方法注入所依賴的bean,而在所依賴的bean使用了構(gòu)造函數(shù)注入主bean對象,這種情況則不會出現(xiàn)循環(huán)依賴問題。
@RestController
public class AccountController {
private static final Logger LOG = LoggerFactory.getLogger(AccountController.class);
// 屬性值注入
@Autowired
private AccountService accountService;
}
@Service
public class AccountService {
private AccountController accountController;
// 構(gòu)造函數(shù)注入
@Autowired
public AccountService(AccountController accountController) {
this.accountController = accountController;
}
}
2.總結(jié)
- 當存在循環(huán)依賴時,主bean對象不能通過構(gòu)造函數(shù)的方式注入所依賴的bean對象,而所依賴的bean對象則不受限制,即可以通過三種注入方式的任意一種注入主bean對象。
- 如果主bean對象通過構(gòu)造函數(shù)方式注入所依賴的bean對象,則無論所依賴的bean對象通過何種方式注入主bean,都無法解決循環(huán)依賴問題,程序無法啟動。(其實在主bean加上@Lazy也能解決)
原因主要是主bean對象通過構(gòu)造函數(shù)注入所依賴bean對象時,無法創(chuàng)建該所依賴的bean對象,獲取該所依賴bean對象的引用。因為如下代碼所示。
創(chuàng)建主bean對象,調(diào)用順序為:
- 1.調(diào)用構(gòu)造函數(shù)
- 2. 放到三級緩存
- 3. 屬性賦值。其中調(diào)用構(gòu)造函數(shù)時會觸發(fā)所依賴的bean對象的創(chuàng)建。
// bean對象實例創(chuàng)建的核心實現(xiàn)方法
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
// 省略其他代碼
// 1. 調(diào)用構(gòu)造函數(shù)創(chuàng)建該bean對象,若不存在構(gòu)造函數(shù)注入,順利通過
instanceWrapper = createBeanInstance(beanName, mbd, args);
// 2. 在singletonFactories緩存中,放入該bean對象,以便解決循環(huán)依賴問題
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
// 3. populateBean方法:bean對象的屬性賦值
populateBean(beanName, mbd, instanceWrapper);
// 省略其他代碼
return exposedObject;
}
createBeanInstance是調(diào)用構(gòu)造函數(shù)創(chuàng)建主bean對象,在里面會注入構(gòu)造函數(shù)中所依賴的bean,而此時并沒有執(zhí)行到addSingletonFactory方法來添加主bean對象的創(chuàng)建工廠到三級緩存singletonFactories中。故在createBeanInstance內(nèi)部,注入和創(chuàng)建該主bean對象時,如果在構(gòu)造函數(shù)中存在對其他bean對象的依賴,并且該bean對象也存在對主bean對象的依賴,則會出現(xiàn)循環(huán)依賴問題,原理如下:
主bean對象為A,A對象依賴于B對象,B對象也存在對A對象的依賴,創(chuàng)建A對象時,會觸發(fā)B對象的創(chuàng)建,則B無法通過三級緩存機制獲取主bean對象A的引用(即B如果通過構(gòu)造函數(shù)注入A,則無法創(chuàng)建B對象;如果通過屬性注入或者setter方法注入A,則創(chuàng)建B對象后,對B對象進行屬性賦值,會卡在populateBean方法也無法返回)。 故無法創(chuàng)建主bean對象所依賴的B,創(chuàng)建主bean對象A時,createBeanInstance方法無法返回,出現(xiàn)代碼死鎖,程序報循環(huán)依賴錯誤。
注意:spring的循環(huán)依賴其實是可以關(guān)閉的,設(shè)置allowCircularReference=false
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
解決參數(shù)命名不規(guī)范,造成使用@NotNull進行校驗出現(xiàn)的問題
這篇文章主要介紹了解決參數(shù)命名不規(guī)范,造成使用@NotNull進行校驗出現(xiàn)的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
java線程安全鎖ReentrantReadWriteLock原理分析readLock
這篇文章主要為大家介紹了java線程安全鎖ReentrantReadWriteLock原理分析readLock,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10
簡單聊一聊Java線程池ThreadPoolExecutor
在使用線程池之后,開啟線程就變成了在線程池當中找到一個空閑的線程,銷毀線程變成了歸還線程到線程池的過程,下面這篇文章主要給大家介紹了關(guān)于Java線程池ThreadPoolExecutor的相關(guān)資料,需要的朋友可以參考下2022-06-06
MybatisPlus查詢條件空字符串和NULL問題背景分析
文章詳細分析了MybatisPlus在處理查詢條件時,空字符串和NULL值的問題,MP 3.3.0及以上版本提供了多種解決方法,包括在Bean屬性上使用注解、全局配置等,推薦使用全局配置的方式來解決這個問題,以避免在SQL查詢中出現(xiàn)不必要的空字符串條件,感興趣的朋友跟隨小編一起看看吧2025-03-03

