Java @Async注解導(dǎo)致spring啟動(dòng)失敗解決方案詳解
前言
在這篇文章里,最后總結(jié)處,我說(shuō)了會(huì)講講循環(huán)依賴中,其中一個(gè)類添加@Async有可能會(huì)導(dǎo)致注入失敗而拋異常的情況,今天就分析一下。
一、異常表現(xiàn),拋出內(nèi)容
1.1循環(huán)依賴的兩個(gè)class
1.CycleService1
@Service
public class CycleService1 {
@Autowired
private CycleService2 cycleService2;
@WangAnno
@Async
public void doThings() {
System.out.println("it's a async move");
}
}
2.CycleService2
@Service
public class CycleService2 {
private CycleService1 cycleService1;
public void init() {
}
@WangAnno
public void alsoDo() {
System.out.println("create cycleService2");
}
}
1.2 啟動(dòng)報(bào)錯(cuò)
Bean with name ‘cycleService1' has been injected into other beans [cycleService2] 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.
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cycleService1': Bean with name 'cycleService1' has been injected into other beans [cycleService2] 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.
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cycleService1': Bean with name 'cycleService1' has been injected into other beans [cycleService2] 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.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:654)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:851)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:884)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:552)
at com.wang.Test.main(Test.java:109)
二、原因分析
2.1 主要原因
想想我在這篇博客里的圖解步驟:
- 當(dāng)Spring在進(jìn)行bean的實(shí)例化的時(shí)候,由于CycleService1和CycleService2是循環(huán)依賴的,
- 同時(shí),由于CycleService1創(chuàng)建早于CycleService2。
- 所以,在CycleService1對(duì)CycleService2的initializeBean方法執(zhí)行之后得到了exposedObject,要從二級(jí)緩存里獲取CycleService1的earlySingletonReference不為null,就需要比較exposedObject和raw CycleService是否還是同一個(gè)對(duì)象,如果不再是同一個(gè)對(duì)象,那么就會(huì)報(bào)錯(cuò)。
- 為什么有這個(gè)邏輯呢?
- 其實(shí)是因?yàn)槿绻軓亩?jí)緩存里拿出的earlySingletonReference不為null,說(shuō)明了在該對(duì)象再創(chuàng)建過(guò)程中被其他對(duì)象循環(huán)依賴了,且調(diào)用了三級(jí)工廠中該對(duì)象的ObjectFactory方法,基于raw bean生成了對(duì)象放入到了二級(jí)緩存。但是當(dāng)raw bean執(zhí)行完initializeBean之后生成了新的對(duì)象,那就出問(wèn)題了。如下圖:

也就是說(shuō)基于raw bean,得到了兩個(gè)基于該raw bean生成的proxy對(duì)象,Spring容器不知道最終該在容器里保存哪一個(gè)了。
2.2 循環(huán)依賴放入二級(jí)緩存處邏輯
1.每個(gè)bean在進(jìn)行屬性注入之前,默認(rèn)都會(huì)往Spring容器中放入一個(gè)ObjectFactory進(jìn)入三級(jí)工廠,以便自己在屬性注入的時(shí)候被循環(huán)依賴時(shí)調(diào)用生成對(duì)象
if (earlySingletonExposure) {
// 返回一個(gè)進(jìn)行了aop處理的ObjectFactory,提前暴露
// 但是只有當(dāng)該實(shí)例在創(chuàng)建過(guò)程中還被其他實(shí)例引用(循環(huán)依賴),才會(huì)被調(diào)用getEarlyBeanReference
// 此處是第四次調(diào)用beanPostProcessor,不一定會(huì)調(diào)用,只有當(dāng)該類真的在創(chuàng)建過(guò)程中被其他類當(dāng)做屬性所依賴
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
2.所以在創(chuàng)建CycleService1過(guò)程中,CycleService2去注入CycleService2之前在三級(jí)工廠里放入了自己的ObjectFactory對(duì)象,然后在CycleService2創(chuàng)建過(guò)程中,要注入CycleService1的時(shí)候,就會(huì)調(diào)用Spring容器中的getEarlyBeanReference(beanName, mbd, bean)獲取CycleService1,下面我們來(lái)看看該方法調(diào)用的具體邏輯
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;
}

然后咱們debug發(fā)現(xiàn),只有AbstractAutoProxyCreator#getEarlyBeanReference方法,有具體實(shí)現(xiàn)邏輯
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
具體的細(xì)節(jié),我們就不進(jìn)入的,主要就是通過(guò)調(diào)用wrapIfNecessary生成了raw bean的aop proxy bean,后面放入了二級(jí)緩存。
2.3 initializeBean生成的對(duì)象
在initializeBean方法里會(huì)調(diào)用applyBeanPostProcessorsAfterInitialization方法
@Override
public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
throws BeansException {
Object result = existingBean;
for (BeanPostProcessor processor : getBeanPostProcessors()) {
Object current = processor.postProcessAfterInitialization(result, beanName);
if (current == null) {
return result;
}
result = current;
}
return result;
}
在循環(huán)里面會(huì)調(diào)用postProcessAfterInitialization方法
重點(diǎn)關(guān)注AbstractAutoProxyCreator的該方法實(shí)現(xiàn):
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
// 對(duì)bean進(jìn)行proxy操作
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
會(huì)發(fā)現(xiàn)AbstractAutoProxyCreator#postProcessAfterInitialization里面的具體邏輯就是判斷這個(gè)類有沒(méi)有調(diào)用過(guò)wrapIfNecessary,如果調(diào)用過(guò)就不再調(diào)用,就是保證同一個(gè)raw bean不會(huì)被多次proxy,同時(shí)提前暴露注入到其他對(duì)象里的就是proxy bean。
但是由于該bean(CycleService1)上加了@Async注解,此次也會(huì)觸發(fā)AsyncAnnotationBeanPostProcessor#postProcessAfterInitialization,而這個(gè)方法,我們?cè)?a href="http://www.dhdzp.com/article/221542.htm" target="_blank">這篇文章里講過(guò)了,正是@Async注解能生效的關(guān)鍵邏輯。所以此處生成了一個(gè)具有Async功能的新的async proxy bean
2.4 再次分析原因
基于2.3和2.4,我們基于raw bean得到了二級(jí)緩存里的aop proxy bean和async proxy bean。
讓我們?cè)倩貞浺幌屡袛噙壿嫞?/p>
//此處是從二級(jí)緩存里面根據(jù)beanName拿出對(duì)象,因?yàn)槎?jí)緩存里放入的是因?yàn)檠h(huán)依賴給其他bean注入的代理對(duì)象
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
// 我們之前早期暴露出去的Bean跟現(xiàn)在最后要放到容器中的Bean不是同一個(gè)
// allowRawInjectionDespiteWrapping為false
// 并且當(dāng)前Bean被當(dāng)成依賴注入到了別的Bean中
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 獲取到當(dāng)前Bean依賴的Bean
String[] dependentBeans = getDependentBeans(beanName);
// 要得到真實(shí)的依賴的Bean
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
// 移除那些僅僅為了類型檢查而創(chuàng)建出來(lái)
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.");
}
}
}
簡(jiǎn)而言之,也就是此時(shí)從二級(jí)緩存里拿到了aop proxy bean,同時(shí)了執(zhí)行完initializeBean之后,raw bean變?yōu)榱薬sync proxybean,Spring容器基于raw bean得到了兩個(gè)proxy bean,無(wú)法處理了。所以在使用@Async注解時(shí),盡量不要在被循環(huán)依賴的Class上添加
解決方案
打破循環(huán)依賴
目前我能想到的方法就是打破循環(huán)依賴,因?yàn)檠h(huán)依賴發(fā)生在bean生命周期的–屬性注入階段,所以我們需要做的就是打破這種循環(huán)依賴
1.延遲注入(使用@Lazy注解)
@Service
public class CycleService1 {
@Lazy
@Autowired
private CycleService2 cycleService2;
@WangAnno
@Async
public void doThings() {
cycleService2.alsoDo();
System.out.println("it's a async move");
}
}
看過(guò)這篇文章的都知道原理了,此處不再累贅
2. 手動(dòng)延遲注入(使用applicationContext.getBean)
@Service
public class CycleService1 {
@Autowired
private ApplicationContext applicationContext;
private CycleService2 cycleService2;
@WangAnno
@Async
public void doThings() {
if (Objects.isNull(cycleService2)) {
cycleService2 = applicationContext.getBean(CycleService2.class);
}
cycleService2.alsoDo();
System.out.println("it's a async move");
}
}
其實(shí)效果是上面加了@Lazy效果是一樣的,不過(guò)是我們自己在方法執(zhí)行的過(guò)程中手動(dòng)進(jìn)行延遲注入而已。
總結(jié)

從二級(jí)緩存里拿到earlySingletonReference(aop proxy bean),同時(shí)了執(zhí)行完initializeBean之后,raw bean變?yōu)榱薳xposedObject(async proxy bean),Spring容器基于raw bean得到了兩個(gè)proxy bean,無(wú)法處理了。
所以在使用@Async注解時(shí),盡量不要在被循環(huán)依賴的Class上添加
實(shí)在非要添加,可以看看我給出的解決方法。
到此這篇關(guān)于Java @Async注解導(dǎo)致spring啟動(dòng)失敗解決方案詳解的文章就介紹到這了,更多相關(guān)Java @Async注解導(dǎo)致spring啟動(dòng)失敗解決方案內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java多態(tài)中的向上轉(zhuǎn)型與向下轉(zhuǎn)型淺析
多態(tài)是指不同類的對(duì)象在調(diào)用同一個(gè)方法是所呈現(xiàn)出的多種不同行為,下面這篇文章主要給大家介紹了關(guān)于Java多態(tài)中向上轉(zhuǎn)型與向下轉(zhuǎn)型的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02
SpringBoot+WebSocket實(shí)現(xiàn)即時(shí)通訊功能(J2EE方式)
WebSocket是一種在單個(gè)TCP連接上進(jìn)行全雙工通信的協(xié)議,WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡(jiǎn)單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù),本文給大家介紹了SpringBoot+WebSocket實(shí)現(xiàn)即時(shí)通訊功能(J2EE方式),需要的朋友可以參考下2025-01-01
Java實(shí)現(xiàn)批量修改文件名和重命名的方法
這篇文章主要介紹了Java實(shí)現(xiàn)批量修改文件名和重命名的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
Android開(kāi)發(fā)中實(shí)現(xiàn)用戶注冊(cè)和登陸的代碼實(shí)例分享
這篇文章主要介紹了Android開(kāi)發(fā)中實(shí)現(xiàn)用戶注冊(cè)和登陸的代碼實(shí)例分享,只是實(shí)現(xiàn)基本功能,界面華麗度就請(qǐng)忽略啦XD 需要的朋友可以參考下2015-12-12

