解决Spring Boot项目中使用@Async注解时遇到的问题
**解决 Spring Boot 项目中使用 @Async 注解时遇到的循环依赖问题** Spring Boot 中使用 @Async 注解时,可能会遇到循环依赖问题,导致 BeanCurrentlyInCreationException 异常。这是因为 @Async 注解使用动态代理实现异步任务,而动态代理的处理过程与循环依赖的解决方式存在冲突。 为了避免循环依赖,Spring 采用了三级缓存机制,其中早期公开引用对象是循环依赖解决的关键。然而,由于 @Async 注解的动态代理处理发生在 Bean 初始化之后,导致早期公开引用对象与最终创建的对象不一致,从而引发循环依赖异常。 要解决此问题,有以下几种方法: * **调整对象依赖关系:**根除循环依赖,避免早期公开引用对象与最终创建对象的差异。 * **不使用 @Async 注解:**自行实现异步任务,不依赖动态代理。 * **使用 @Lazy 注解:**在循环依赖注入的字段上加 @Lazy 注解,延迟对象创建,避免早期暴露。 * **设置 allowRawInjectionDespiteWrapping:**将 allowRawInjectionDespiteWrapping 设置为 true,允许早期注入的对象和最终创建的对象不一致,但可能会导致动态代理失效。
背景
前段时间,一个同事小姐姐跟我说她的项目起不来了,让我帮忙看一下,本着助人为乐的精神,这个忙肯定要去帮。
于是,我在她的控制台发现了如下的异常信息:
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这个异常,我的第一反应是出现了循环依赖的问题。但是仔细一想,Spring不是已经解决了循环依赖的问题么,怎么还报这个错。于是,我就询问小姐姐改了什么东西,她说在方法上加了@Async注解。
这里我模拟一下当时的代码,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注解遇上循环依赖的时候,Spring无法解决?为了验证这个猜想,我将@Async注解去掉之后,再次启动项目,项目成功起来了。于是基本可以得出结论,那就是@Async注解遇上循环依赖的时候,Spring的确无法解决。
虽然问题的原因已经找到了,但是又引出以下几个问题:
@Async注解是如何起作用的?
为什么@Async注解遇上循环依赖,Spring无法解决?
出现循环依赖异常之后如何解决?
@Async注解是如何起作用的?
@Async注解起作用是靠AsyncAnnotationBeanPostProcessor这个类实现的,这个类会处理@Async注解。AsyncAnnotationBeanPostProcessor这个类的对象是由@EnableAsync注解放入到Spring容器的,这也是为什么需要使用@EnableAsync注解来激活让@Async注解起作用的根本原因。
AsyncAnnotationBeanPostProcessor

类体系
这个类实现了 BeanPostProcessor 接口,实现了 postProcessAfterInitialization 方法,是在其父类AbstractAdvisingBeanPostProcessor 中实现的,也就是说当Bean的初始化阶段完成之后会回调 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法。之所以会回调,是因为在Bean的生命周期中,当Bean初始化完成之后,会回调所有的 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 方法实现:
@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;
}该方法的主要作用是用来对方法入参的对象进行动态代理的,当入参的对象的类加了@Async注解,那么这个方法就会对这个对象进行动态代理,最后会返回入参对象的代理对象出去。至于如何判断方法有没有加@Async注解,是靠 isEligible(bean, beanName) 来判断的。由于这段代码牵扯到动态代理底层的知识,这里就不详细展开了。

AsyncAnnotationBeanPostProcessor作用
综上所述,可以得出一个结论,那就是当Bean创建过程中初始化阶段完成之后,会调用 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 的方法,对加了@Async注解的类的对象进行动态代理,然后返回一个代理对象回去。
虽然这里我们得出@Async注解的作用是依靠动态代理实现的,但是这里其实又引发了另一个问题,那就是事务注解@Transactional又或者是自定义的AOP切面,他们也都是通过动态代理实现的,为什么使用这些的时候,没见抛出循环依赖的异常?难道他们的实现跟@Async注解的实现不一样?不错,还真的不太一样,请继续往下看。
AOP是如何实现的?
我们都知道AOP是依靠动态代理实现的,而且是在Bean的生命周期中起作用,具体是靠 AnnotationAwareAspectJAutoProxyCreator 这个类实现的,这个类会在Bean的生命周期中去处理切面,事务注解,然后生成动态代理。这个类的对象在容器启动的时候,就会被自动注入到Spring容器中。
AnnotationAwareAspectJAutoProxyCreator 也实现了BeanPostProcessor,也实现了 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)) {
//生成动态代理,如果需要被代理的话
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}通过 wrapIfNecessary 方法就会对Bean进行动态代理,如果你的Bean需要被动态代理的话。

AnnotationAwareAspectJAutoProxyCreator作用
也就说,AOP和@Async注解虽然底层都是动态代理,但是具体实现的类是不一样的。一般的AOP或者事务的动态代理是依靠 AnnotationAwareAspectJAutoProxyCreator 实现的,而@Async是依靠 AsyncAnnotationBeanPostProcessor 实现的,并且都是在初始化完成之后起作用,这也就是@Async注解和AOP之间的主要区别,也就是处理的类不一样。
Spring是如何解决循环依赖的
Spring在解决循环依赖的时候,是依靠三级缓存来实现的。我曾经写过一篇关于三级缓存的文章,如果有不清楚的小伙伴可以 关注微信公众号 三友的java日记,回复 循环依赖 即可获取原文链接,本文也算是这篇三级缓存文章的续作。
简单来说,通过缓存正在创建的对象对应的ObjectFactory对象,可以获取到正在创建的对象的早期引用的对象,当出现循环依赖的时候,由于对象没创建完,就可以通过获取早期引用的对象注入就行了。
而缓存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表达式,真正获取早期暴露的引用对象其实就是通过 getEarlyBeanReference 方法来实现的。
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 实现是调用所有的 SmartInstantiationAwareBeanPostProcessor 的 getEarlyBeanReference 方法。
而前面提到的 AnnotationAwareAspectJAutoProxyCreator 这个类就实现了 SmartInstantiationAwareBeanPostProcessor 接口,是在父类中实现的:
@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);
}这个方法最后会调用 wrapIfNecessary 方法,前面也说过,这个方法是获取动态代理的方法,如果需要的话就会代理,比如事务注解又或者是自定义的AOP切面,在早期暴露的时候,就会完成动态代理。
这下终于弄清楚了,早期暴露出去的原来可能是个代理对象,而且最终是通过AnnotationAwareAspectJAutoProxyCreator这个类的getEarlyBeanReference方法获取的。
但是AsyncAnnotationBeanPostProcessor并没有实现SmartInstantiationAwareBeanPostProcessor,也就是在获取早期对象这一阶段,并不会调AsyncAnnotationBeanPostProcessor处理@Async注解。
为什么@Async注解遇上循环依赖,Spring无法解决?
这里我们就拿前面的例子来说,AService加了@Async注解,AService先创建,发现引用了BService,那么BService就会去创建,当Service创建的过程中发现引用了AService,那么就会通过AnnotationAwareAspectJAutoProxyCreator 这个类实现的 getEarlyBeanReference 方法获取AService的早期引用对象,此时这个早期引用对象可能会被代理,取决于AService是否需要被代理,但是一定不是处理@Async注解的代理,原因前面也说过。
于是BService创建好之后,注入给了AService,那么AService会继续往下处理,前面说过,当初始化阶段完成之后,会调用所有的BeanPostProcessor的实现的 postProcessAfterInitialization 方法。于是就会回调依次回调 AnnotationAwareAspectJAutoProxyCreator 和 AsyncAnnotationBeanPostProcessor 的 postProcessAfterInitialization 方法实现。
这段回调有两个细节:
AnnotationAwareAspectJAutoProxyCreator 先执行,AsyncAnnotationBeanPostProcessor 后执行,因为 AnnotationAwareAspectJAutoProxyCreator 在前面。

AnnotationAwareAspectJAutoProxyCreator处理的结果会当入参传递给 AsyncAnnotationBeanPostProcessor,applyBeanPostProcessorsAfterInitialization方法就是这么实现的
AnnotationAwareAspectJAutoProxyCreator回调:会发现AService对象已经被早期引用了,什么都不处理,直接把对象AService给返回
AsyncAnnotationBeanPostProcessor回调:发现AService类中加了@Async注解,那么就会对AnnotationAwareAspectJAutoProxyCreator返回的对象进行动态代理,然后返回了动态代理对象。
这段回调完,是不是已经发现了问题。早期暴露出去的对象,可能是AService本身或者是AService的代理对象,而且是通过AnnotationAwareAspectJAutoProxyCreator对象实现的,但是通过AsyncAnnotationBeanPostProcessor的回调,会对AService对象进行动态代理,这就导致AService早期暴露出去的对象跟最后完全创造出来的对象不是同一个,那么肯定就不对了。
同一个Bean在一个Spring中怎么能存在两个不同的对象呢,于是就会抛出BeanCurrentlyInCreationException异常,这段判断逻辑的代码如下:
if (earlySingletonExposure) {
// 获取到早期暴露出去的对象
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
// 早期暴露的对象不为null,说明出现了循环依赖
if (exposedObject == bean) {
// 这个判断的意思就是指 postProcessAfterInitialization 回调没有进行动态代理,如果没有那么就将早期暴露出去的对象赋值给最终暴露(生成)出去的对象,
// 这样就实现了早期暴露出去的对象和最终生成的对象是同一个了
// 但是一旦 postProcessAfterInitialization 回调生成了动态代理 ,那么就不会走这,也就是加了@Aysnc注解,是不会走这的
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// allowRawInjectionDespiteWrapping 默认是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注解遇上循环依赖,Spring无法解决,是因为@Aysnc注解会使得最终创建出来的Bean,跟早期暴露出去的Bean不是同一个对象,所以就会报错。
出现循环依赖异常之后如何解决?
解决这个问题的方法很多
1、调整对象间的依赖关系,从根本上杜绝循环依赖,没有循环依赖,就没有早期暴露这么一说,那么就不会出现问题
2、不使用@Async注解,可以自己通过线程池实现异步,这样没有@Async注解,就不会在最后生成代理对象,导致早期暴露的出去的对象不一样
3、可以在循环依赖注入的字段上加@Lazy注解
@Component
public class AService {
@Resource
@Lazy
private BService bService;
@Async
public void save() {
}
}4、从上面的那段判断抛异常的源码注释可以看出,当allowRawInjectionDespiteWrapping为true的时候,就不会走那个else if,也就不会抛出异常,所以可以通过将allowRawInjectionDespiteWrapping设置成true来解决报错的问题,代码如下:
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
((DefaultListableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
}
}虽然这样设置能解决报错的问题,但是并不推荐,因为这样设置就允许早期注入的对象和最终创建出来的对象是不一样,并且可能会导致最终生成的对象没有被动态代理。
今天关于《解决Spring Boot项目中使用@Async注解时遇到的问题》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!
减少Python代码中的过多错误数量的解决方法
- 上一篇
- 减少Python代码中的过多错误数量的解决方法
- 下一篇
- Laravel和Slim:哪个框架更适合开发移动应用?
-
- 文章 · java教程 | 5分钟前 |
- Jackson解析JSON教程:Java实战指南
- 427浏览 收藏
-
- 文章 · java教程 | 7分钟前 |
- Collections.addAll方法使用全解析
- 162浏览 收藏
-
- 文章 · java教程 | 11分钟前 |
- Java字符串转数字异常处理方法
- 179浏览 收藏
-
- 文章 · java教程 | 29分钟前 | java 线程中断 interrupt() isInterrupted() interrupted()
- Java如何判断线程是否中断
- 269浏览 收藏
-
- 文章 · java教程 | 33分钟前 |
- JavaCollections.addAll使用方法与注意事项
- 176浏览 收藏
-
- 文章 · java教程 | 48分钟前 |
- Java收支统计系统控制台实战教程
- 342浏览 收藏
-
- 文章 · java教程 | 52分钟前 |
- Java实现投票统计功能教程
- 402浏览 收藏
-
- 文章 · java教程 | 54分钟前 |
- Java多态与动态绑定的关系解析
- 374浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java开发投票权限管理项目实战教程
- 286浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java多线程基础:轻松理解线程概念
- 487浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java方法重构:接口与助手统一处理多参数
- 139浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java正则表达式使用与替换详解
- 489浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3211次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3425次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3454次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4563次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3832次使用
-
- 提升Java功能开发效率的有力工具:微服务架构
- 2023-10-06 501浏览
-
- 掌握Java海康SDK二次开发的必备技巧
- 2023-10-01 501浏览
-
- 如何使用java实现桶排序算法
- 2023-10-03 501浏览
-
- Java开发实战经验:如何优化开发逻辑
- 2023-10-31 501浏览
-
- 如何使用Java中的Math.max()方法比较两个数的大小?
- 2023-11-18 501浏览

