spring?boot項目使用@Async注解的坑
背景
前段時間,一個同事小姐姐跟我說她的項目起不來了,讓我?guī)兔匆幌?,本著助人為樂的精神,這個忙肯定要去幫。
于是,我在她的控制臺發(fā)現(xiàn)了如下的異常信息:
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'AService': Bean with name 'AService' has been injected into other beans [BService] 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:602)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
看到BeanCurrentlyInCreationException這個異常,我的第一反應(yīng)是出現(xiàn)了循環(huán)依賴的問題。但是仔細(xì)一想,Spring不是已經(jīng)解決了循環(huán)依賴的問題么,怎么還報這個錯。于是,我就詢問小姐姐改了什么東西,她說在方法上加了@Async注解。
這里我模擬一下當(dāng)時的代碼,AService 和 BService 相互引用,AService的 save() 方法加了 @Async 注解。
@Component
public class AService {
@Resource
private BService bService;
@Async
public void save() {
}
}
@Component
public class BService {
@Resource
private AService aService;
}
也就是這段代碼會報BeanCurrentlyInCreationException異常,難道是@Async注解遇上循環(huán)依賴的時候,Spring無法解決?為了驗證這個猜想,我將@Async注解去掉之后,再次啟動項目,項目成功起來了。于是基本可以得出結(jié)論,那就是@Async注解遇上循環(huán)依賴的時候,Spring的確無法解決。
雖然問題的原因已經(jīng)找到了,但是又引出以下幾個問題:
- @Async注解是如何起作用的?
- 為什么@Async注解遇上循環(huán)依賴,Spring無法解決?
- 出現(xiàn)循環(huán)依賴異常之后如何解決?
@Async注解是如何起作用的?
@Async注解起作用是靠AsyncAnnotationBeanPostProcessor這個類實現(xiàn)的,這個類會處理@Async注解。AsyncAnnotationBeanPostProcessor這個類的對象是由@EnableAsync注解放入到Spring容器的,這也是為什么需要使用@EnableAsync注解來激活讓@Async注解起作用的根本原因。
AsyncAnnotationBeanPostProcessor

類體系
這個類實現(xiàn)了 BeanPostProcessor 接口,實現(xiàn)了 postProcessAfterInitialization 方法,是在其父類AbstractAdvisingBeanPostProcessor 中實現(xiàn)的,也就是說當(dāng)Bean的初始化階段完成之后會回調(diào) AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法。之所以會回調(diào),是因為在Bean的生命周期中,當(dāng)Bean初始化完成之后,會回調(diào)所有的 BeanPostProcessor 的 postProcessAfterInitialization 方法,代碼如下:
@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;
}
AsyncAnnotationBeanPostProcessor 對于 postProcessAfterInitialization 方法實現(xiàn):
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (this.advisor == null || bean instanceof AopInfrastructureBean) {
// Ignore AOP infrastructure such as scoped proxies.
return bean;
}
if (bean instanceof Advised) {
Advised advised = (Advised) bean;
if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
// Add our local Advisor to the existing proxy's Advisor chain...
if (this.beforeExistingAdvisors) {
advised.addAdvisor(0, this.advisor);
}
else {
advised.addAdvisor(this.advisor);
}
return bean;
}
}
if (isEligible(bean, beanName)) {
ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
if (!proxyFactory.isProxyTargetClass()) {
evaluateProxyInterfaces(bean.getClass(), proxyFactory);
}
proxyFactory.addAdvisor(this.advisor);
customizeProxyFactory(proxyFactory);
return proxyFactory.getProxy(getProxyClassLoader());
}
// No proxy needed.
return bean;
}
該方法的主要作用是用來對方法入?yún)⒌膶ο筮M(jìn)行動態(tài)代理的,當(dāng)入?yún)⒌膶ο蟮念惣恿薂Async注解,那么這個方法就會對這個對象進(jìn)行動態(tài)代理,最后會返回入?yún)ο蟮拇韺ο蟪鋈?。至于如何判斷方法有沒有加@Async注解,是靠 isEligible(bean, beanName) 來判斷的。由于這段代碼牽扯到動態(tài)代理底層的知識,這里就不詳細(xì)展開了。

AsyncAnnotationBeanPostProcessor作用
綜上所述,可以得出一個結(jié)論,那就是當(dāng)Bean創(chuàng)建過程中初始化階段完成之后,會調(diào)用 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 的方法,對加了@Async注解的類的對象進(jìn)行動態(tài)代理,然后返回一個代理對象回去。
雖然這里我們得出@Async注解的作用是依靠動態(tài)代理實現(xiàn)的,但是這里其實又引發(fā)了另一個問題,那就是事務(wù)注解@Transactional又或者是自定義的AOP切面,他們也都是通過動態(tài)代理實現(xiàn)的,為什么使用這些的時候,沒見拋出循環(huán)依賴的異常?難道他們的實現(xiàn)跟@Async注解的實現(xiàn)不一樣?不錯,還真的不太一樣,請繼續(xù)往下看。
AOP是如何實現(xiàn)的?
我們都知道AOP是依靠動態(tài)代理實現(xiàn)的,而且是在Bean的生命周期中起作用,具體是靠 AnnotationAwareAspectJAutoProxyCreator 這個類實現(xiàn)的,這個類會在Bean的生命周期中去處理切面,事務(wù)注解,然后生成動態(tài)代理。這個類的對象在容器啟動的時候,就會被自動注入到Spring容器中。
AnnotationAwareAspectJAutoProxyCreator 也實現(xiàn)了BeanPostProcessor,也實現(xiàn)了 postProcessAfterInitialization 方法。
@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
//生成動態(tài)代理,如果需要被代理的話
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
通過 wrapIfNecessary 方法就會對Bean進(jìn)行動態(tài)代理,如果你的Bean需要被動態(tài)代理的話。

AnnotationAwareAspectJAutoProxyCreator作用
也就說,AOP和@Async注解雖然底層都是動態(tài)代理,但是具體實現(xiàn)的類是不一樣的。一般的AOP或者事務(wù)的動態(tài)代理是依靠 AnnotationAwareAspectJAutoProxyCreator 實現(xiàn)的,而@Async是依靠 AsyncAnnotationBeanPostProcessor 實現(xiàn)的,并且都是在初始化完成之后起作用,這也就是@Async注解和AOP之間的主要區(qū)別,也就是處理的類不一樣。
Spring是如何解決循環(huán)依賴的
Spring在解決循環(huán)依賴的時候,是依靠三級緩存來實現(xiàn)的。我曾經(jīng)寫過一篇關(guān)于三級緩存的文章,如果有不清楚的小伙伴可以 關(guān)注微信公眾號 三友的java日記,回復(fù) 循環(huán)依賴 即可獲取原文鏈接,本文也算是這篇三級緩存文章的續(xù)作。
簡單來說,通過緩存正在創(chuàng)建的對象對應(yīng)的ObjectFactory對象,可以獲取到正在創(chuàng)建的對象的早期引用的對象,當(dāng)出現(xiàn)循環(huán)依賴的時候,由于對象沒創(chuàng)建完,就可以通過獲取早期引用的對象注入就行了。
而緩存ObjectFactory代碼如下:
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
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);
}
}
}
所以緩存的ObjectFactory對象其實是一個lamda表達(dá)式,真正獲取早期暴露的引用對象其實就是通過 getEarlyBeanReference 方法來實現(xiàn)的。
getEarlyBeanReference 方法:
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;
}
getEarlyBeanReference 實現(xiàn)是調(diào)用所有的 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference 方法。
而前面提到的 AnnotationAwareAspectJAutoProxyCreator 這個類就實現(xiàn)了 SmartInstantiationAwareBeanPostProcessor 接口,是在父類中實現(xiàn)的:
@Override
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);
}
這個方法最后會調(diào)用 wrapIfNecessary 方法,前面也說過,這個方法是獲取動態(tài)代理的方法,如果需要的話就會代理,比如事務(wù)注解又或者是自定義的AOP切面,在早期暴露的時候,就會完成動態(tài)代理。
這下終于弄清楚了,早期暴露出去的原來可能是個代理對象,而且最終是通過AnnotationAwareAspectJAutoProxyCreator這個類的getEarlyBeanReference方法獲取的。
但是AsyncAnnotationBeanPostProcessor并沒有實現(xiàn)SmartInstantiationAwareBeanPostProcessor,也就是在獲取早期對象這一階段,并不會調(diào)AsyncAnnotationBeanPostProcessor處理@Async注解。
為什么@Async注解遇上循環(huán)依賴,Spring無法解決?
這里我們就拿前面的例子來說,AService加了@Async注解,AService先創(chuàng)建,發(fā)現(xiàn)引用了BService,那么BService就會去創(chuàng)建,當(dāng)Service創(chuàng)建的過程中發(fā)現(xiàn)引用了AService,那么就會通過AnnotationAwareAspectJAutoProxyCreator 這個類實現(xiàn)的 getEarlyBeanReference 方法獲取AService的早期引用對象,此時這個早期引用對象可能會被代理,取決于AService是否需要被代理,但是一定不是處理@Async注解的代理,原因前面也說過。
于是BService創(chuàng)建好之后,注入給了AService,那么AService會繼續(xù)往下處理,前面說過,當(dāng)初始化階段完成之后,會調(diào)用所有的BeanPostProcessor的實現(xiàn)的 postProcessAfterInitialization 方法。于是就會回調(diào)依次回調(diào) AnnotationAwareAspectJAutoProxyCreator 和 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法實現(xiàn)。
這段回調(diào)有兩個細(xì)節(jié):
- AnnotationAwareAspectJAutoProxyCreator 先執(zhí)行,AsyncAnnotationBeanPostProcessor 后執(zhí)行,因為 AnnotationAwareAspectJAutoProxyCreator 在前面。

- AnnotationAwareAspectJAutoProxyCreator處理的結(jié)果會當(dāng)入?yún)鬟f給 AsyncAnnotationBeanPostProcessor,applyBeanPostProcessorsAfterInitialization方法就是這么實現(xiàn)的
AnnotationAwareAspectJAutoProxyCreator回調(diào):會發(fā)現(xiàn)AService對象已經(jīng)被早期引用了,什么都不處理,直接把對象AService給返回
AsyncAnnotationBeanPostProcessor回調(diào):發(fā)現(xiàn)AService類中加了@Async注解,那么就會對AnnotationAwareAspectJAutoProxyCreator返回的對象進(jìn)行動態(tài)代理,然后返回了動態(tài)代理對象。
這段回調(diào)完,是不是已經(jīng)發(fā)現(xiàn)了問題。早期暴露出去的對象,可能是AService本身或者是AService的代理對象,而且是通過AnnotationAwareAspectJAutoProxyCreator對象實現(xiàn)的,但是通過AsyncAnnotationBeanPostProcessor的回調(diào),會對AService對象進(jìn)行動態(tài)代理,這就導(dǎo)致AService早期暴露出去的對象跟最后完全創(chuàng)造出來的對象不是同一個,那么肯定就不對了。
同一個Bean在一個Spring中怎么能存在兩個不同的對象呢,于是就會拋出BeanCurrentlyInCreationException異常,這段判斷邏輯的代碼如下:
if (earlySingletonExposure) {
// 獲取到早期暴露出去的對象
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 早期暴露的對象不為null,說明出現(xiàn)了循環(huán)依賴
if (exposedObject == bean) {
// 這個判斷的意思就是指 postProcessAfterInitialization 回調(diào)沒有進(jìn)行動態(tài)代理,如果沒有那么就將早期暴露出去的對象賦值給最終暴露(生成)出去的對象,
// 這樣就實現(xiàn)了早期暴露出去的對象和最終生成的對象是同一個了
// 但是一旦 postProcessAfterInitialization 回調(diào)生成了動態(tài)代理 ,那么就不會走這,也就是加了@Aysnc注解,是不會走這的
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// allowRawInjectionDespiteWrapping 默認(rèn)是false
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(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.");
}
}
}
}
所以,之所以@Async注解遇上循環(huán)依賴,Spring無法解決,是因為@Aysnc注解會使得最終創(chuàng)建出來的Bean,跟早期暴露出去的Bean不是同一個對象,所以就會報錯。
出現(xiàn)循環(huán)依賴異常之后如何解決?
解決這個問題的方法很多
1、調(diào)整對象間的依賴關(guān)系,從根本上杜絕循環(huán)依賴,沒有循環(huán)依賴,就沒有早期暴露這么一說,那么就不會出現(xiàn)問題
2、不使用@Async注解,可以自己通過線程池實現(xiàn)異步,這樣沒有@Async注解,就不會在最后生成代理對象,導(dǎo)致早期暴露的出去的對象不一樣
3、可以在循環(huán)依賴注入的字段上加@Lazy注解
@Component
public class AService {
@Resource
@Lazy
private BService bService;
@Async
public void save() {
}
}
4、從上面的那段判斷拋異常的源碼注釋可以看出,當(dāng)allowRawInjectionDespiteWrapping為true的時候,就不會走那個else if,也就不會拋出異常,所以可以通過將allowRawInjectionDespiteWrapping設(shè)置成true來解決報錯的問題,代碼如下:
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
}
}
雖然這樣設(shè)置能解決報錯的問題,但是并不推薦,因為這樣設(shè)置就允許早期注入的對象和最終創(chuàng)建出來的對象是不一樣,并且可能會導(dǎo)致最終生成的對象沒有被動態(tài)代理。
以上就是spring boot項目使用@Async注解的坑的詳細(xì)內(nèi)容,更多關(guān)于spring boot項目@Async注解的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java 解決異常 2 字節(jié)的 UTF-8 序列的字節(jié)2 無效的問題
這篇文章主要介紹了java 解決異常 2 字節(jié)的 UTF-8 序列的字節(jié) 2 無效的問題的相關(guān)資料,需要的朋友可以參考下2016-12-12
Java實現(xiàn)ArrayList自動擴(kuò)容
ArrayList的擴(kuò)容規(guī)則是非常簡單的,它會根據(jù)需要自動擴(kuò)容,本文就來介紹一下Java實現(xiàn)ArrayList自動擴(kuò)容,具有一定的參考價值,感興趣的可以了解一下2023-12-12

