本文基于spring4.3.9
什么是循環依賴
在spring中一個bean依賴另外一個bean有兩種方式,一是通過構造函數,二是通過字段注入。
用代碼來解釋循環依賴,那么就是以下場景
public class A{
@Autowired
B b;
}
public class B{
@Autowired
A a;
}
或者
public class A{
public A(B b){
}
}
public class B{
public B(A a){
}
}
也可以是
public class A{
public A(B b){
}
}
public class B{
@Autowired
A a;
}
如何解決
循環依賴只存在于singleton類型bean之間
對于構造函數循環依賴的情況,spring無能為力,在獲取bean前spring中會通過下面代碼拋出異常
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
singletonsCurrentlyInCreation會保存當前正在構造中的beanName,錯誤產生邏輯大概如下:
- 我們向BeanFactory獲取A類型的bean
- A類型bean準備構造,把beanName保存到singletonsCurrentlyInCreation
- A類型通過構造函數實例化,依賴B類型的Bean,向BeanFactory請求B類型Bean
- B類型bean準備構造,把beanName保存到singletonsCurrentlyInCreation
- B類型通過構造函數實例化,依賴A類型的Bean,向BeanFactory請求A類型Bean
- 在DefaultSingletonBeanRegistry#getSingleton#beforeSingletonCreation方法的檢查singletonsCurrentlyInCreation是否已經包含當前請求的beanName,拋出異常
而對于字段注入類型或者字段注入和構造函數混合的循環依賴,spring通過緩存解決這個問題。因為其中一個對象是可實例化的!!
我們以字段注入類型的循環依賴為例
- 我們向BeanFactory獲取A類型的bean
- A類型bean實例化,把未初始化的自己放到緩存中
- A類型bean進行構造(populdateBean),觸發了依賴注入B
- 我們向BeanFactory獲取B類型的bean
- B類型bean實例化,把未初始化的自己放到緩存中
- B類型bean進行構造(populdateBean),觸發了依賴注入A
- 從二級緩存中獲取到未初始化的A
- B類型bean進行初始化,返回給第3步的A進行依賴注入
- A類型bean進行初始化
- 返回給調用getBean的方法
這個緩存我們稱它為三級緩存,它的代碼如下,會在doGetBean的開頭被調用
//DefaultSingletonBeanRegistry#getSingleton
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//singletonObjects 第一級緩存,BeanFactory的單例全存在singletonObjects中
//保存的是已經初始化完全的單例
Object singletonObject = this.singletonObjects.get(beanName);
//isSingletonCurrentlyInCreation=true,代表beanName所代表的bean循環依賴了
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//第二級緩存,保存的是未初始化完全的單例(只是實例化)
singletonObject = this.earlySingletonObjects.get(beanName);
//allowEarlyReference在當前場景下,默認為true
if (singletonObject == null && allowEarlyReference) {
//第三級緩存,不是真的緩存,緩存的是生成二級緩存的工廠方法
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//通過三級緩存構造二級緩存
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
在我們實例化bean之后,會把獲取當前bean的方式放入到三級緩存
//AbstractAutowireCapableBeanFactory#doCreateBean
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");
}
//添加三級緩存
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
具體三級緩存構造二級緩存的邏輯如下
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (bean != null && !mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
//這邊可能返回的exposedObject可能不是之前的bean了,生成代理時目前的應用場景
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
if (exposedObject == null) {
return null;
}
}
}
}
return exposedObject;
}
在返回bean前,可能會通過SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference處理一下bean,返回修改后的exposedObject。
這邊是重點?用于解答為啥不是二級緩存而是三級緩存。
因為SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference的實現為AbstractAutoProxyCreator
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
//冪等,防止重復生成代理
if (!this.earlyProxyReferences.contains(cacheKey)) {
this.earlyProxyReferences.add(cacheKey);
}
return wrapIfNecessary(bean, beanName, cacheKey);
}
上面的代碼用于提前對bean生成代理。
按照正常的依賴注入,注入的bean,如果被切面切了,會通過postProcessAfterInitialization轉換為代理對象。而對于循環依賴,會把未初始化完全的bean提前注入,但是可能bean可能是被切面切中的,所以使用第三級緩存中的getEarlyBeanReference發揮作用了,用于提前對未初始化的bean生成代理。
這邊有個問題,提前對未初始化的bean生成代理,會不會影響該bean的正常初始化?
不會。代理對象引用了我們目標bean,目標bean的引用也還是被doCreateBean方法持有的。所以目標bean的初始化還是照常進行。
那么目標bean在執行到postProcessAfterInitialization鉤子的時候,會不會重復生成代理?
不會,AbstractAutoProxyCreator#getEarlyBeanReference中使用earlyProxyReferences做了冪等。
在doGetBean中一直有段代碼看不懂它的意圖,現在也找到答案了
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
//用于將普通bean替換為它的代理對象
exposedObject = earlySingletonReference;
}
//走到這里說明有其他鉤子把bean替換了,所以要檢查在此之前是否已經發生過該bean的依賴注入,如果發生,就導致一個bean的不同版本被注入,針對這種情況,會拋出異常
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<String>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
善始善終,當循環依賴的bean構造好之后,他們的本體bean應該被放到一級緩存中,用于被其他bean獲取。
//DefaultSingletonBeanRegistry#getSingleton#addSingleton
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}