最近在项目中使用@Async注解在方法上引起了循环依赖报错。

代码类似如下:

package com.morris.spring.entity.circular;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;@EnableAsync
public class SingleFieldBeanD {@Autowiredprivate SingleFieldBeanD singleFieldBeanD;@Asyncpublic void test() {System.out.println("test");}
}

报错内容如下:

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'singleFieldBeanD': Bean with name 'singleFieldBeanD' has been injected into other beans [singleFieldBeanD] 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 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:655)
... ...

其实当我们使用Spring的时候,默认是会解决循环依赖,如果不加@Async注解,程序就能正常运行,一旦加上了@Async注解方法后,处理循环依赖就失效了,就会抛出异常。

循环依赖错误原因分析

源码分析,查看异常抛出时的方法:
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {// Instantiate the bean.BeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null) {// 实例化instanceWrapper = createBeanInstance(beanName, mbd, args);}Object bean = instanceWrapper.getWrappedInstance();Class<?> beanType = instanceWrapper.getWrappedClass();if (beanType != NullBean.class) {mbd.resolvedTargetType = beanType;}// Allow post-processors to modify the merged bean definition.synchronized (mbd.postProcessingLock) {if (!mbd.postProcessed) {try {/*** 使用下面的BeanPostProcessor收集带有注解的方法,方便下面初始化属性时调用* AutowiredAnnotationBeanPostProcessor @Autowired @Value* CommonAnnotationBeanPostProcessor @Resource @PostConstruct @PreDestroy*/applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);}catch (Throwable ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Post-processing of merged bean definition failed", ex);}mbd.postProcessed = true;}}// Eagerly cache singletons to be able to resolve circular references// even when triggered by lifecycle interfaces like BeanFactoryAware.// 处理循环依赖,实例化后放入三级缓存boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace("Eagerly caching bean '" + beanName +"' to allow for resolving potential circular references");}// 放入三级缓存addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));}// Initialize the bean instance.Object exposedObject = bean;try {// 初始化bean,为bean赋予属性populateBean(beanName, mbd, instanceWrapper);// 调用初始化方法exposedObject = initializeBean(beanName, exposedObject, mbd);}catch (Throwable ex) {if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {throw (BeanCreationException) ex;}else {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);}}if (earlySingletonExposure) {// 从一级和二级缓存中获取Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {// 不为空说明产生了循环依赖// bean是通过构造方法创建的// 而exposedObject是initializeBean方法返回,这里有可能产生aop代理后返回if (exposedObject == bean) {// 相等说明没有产生aop代理exposedObject = earlySingletonReference;}else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {// 产生了aop代理,且有对象依赖他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 " +"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");}}}}// Register bean as disposable.try {registerDisposableBeanIfNecessary(beanName, bean, mbd);}catch (BeanDefinitionValidationException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);}return exposedObject;
}

举个例子,假设类A和类B通过属性形成了循环依赖:

  1. 创建A:使用构造方法构造A,将A放入三级缓存,给A的属性B复赋值
  2. 创建B:使用构造方法构造B,将B放入三级缓存,给B的属性A复赋值
  3. 此时因为A支持循环依赖,所以会从三级缓存中获取A填充到B的属性中,此时B的属性A持有的是A的原始对象的引用
  4. B完成其他属性的赋值及初始化方法的调用
  5. 继续完成了A的属性的赋值(此时已持有B的实例的引用),继续执行初始化方法initializeBean(…),在此处会解析@Aysnc注解,从而生成一个代理对象,所以最终exposedObject是一个代理对象(而非原始对象)最终加入到容器里
  6. 最后B引用的属性A是个原始对象,而此处准备return的实例A竟然是个代理对象,也就是说B引用的并非是最终对象(不是最终放进容器里的对象),最后抛出异常。

循环依赖问题的解决方案

allowRawInjectionDespiteWrapping设置为true

从源码中可以发现有一个allowRawInjectionDespiteWrapping变量来控制是否进入else if代码段,最后抛出异常,此字段默认为false,如果将此字段设置为true,就不会进入else if代码段,从而就不会抛出异常了。

设置方法如下:

AbstractAutowireCapableBeanFactory beanFactory = (AbstractAutowireCapableBeanFactory) applicationContext.getBeanFactory();
beanFactory.setAllowRawInjectionDespiteWrapping(true);

属性上面添加@Lazy注解

在属性上面添加@Lazy注解就能打破循环依赖,就不会抛出异常了。

代码如下:

package com.morris.spring.entity.circular;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;@EnableAsync
public class SingleFieldBeanD {@Autowired@Lazyprivate SingleFieldBeanD singleFieldBeanD;@Asyncpublic void test() {System.out.println("test");}
}

为什么加上@Lazy就不会抛出异常了呢?因为此时注入的是代理对象,而不是原始对象,不构成循环依赖。

且看源码:

在对属性进行注入时会执行下面的代码:
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject

protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {// buildLazyResourceProxy里面还有调用了getResource方法从Spring容器中获取beanreturn (this.lazyLookup ? buildLazyResourceProxy(this, requestingBeanName) :getResource(this, requestingBeanName));
}

lazyLookup什么时候为true呢?属性上面有@Lazy注解,lazyLookup=true

private class ResourceElement extends LookupElement {private final boolean lazyLookup;public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {super(member, pd);Resource resource = ae.getAnnotation(Resource.class);String resourceName = resource.name();Class<?> resourceType = resource.type();this.isDefaultName = !StringUtils.hasLength(resourceName);if (this.isDefaultName) {resourceName = this.member.getName();if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {resourceName = Introspector.decapitalize(resourceName.substring(3));}}else if (embeddedValueResolver != null) {resourceName = embeddedValueResolver.resolveStringValue(resourceName);}if (Object.class != resourceType) {checkResourceType(resourceType);}else {// No resource type specified... check field/method.resourceType = getResourceType();}this.name = (resourceName != null ? resourceName : "");this.lookupType = resourceType;String lookupValue = resource.lookup();this.mappedName = (StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName());// 有@Lazy注解,lazyLookup=trueLazy lazy = ae.getAnnotation(Lazy.class);this.lazyLookup = (lazy != null && lazy.value());}
... ...

属性上面如果有@Lazy注解,lazyLookup=true,就会创建代理:

protected Object buildLazyResourceProxy(final LookupElement element, final @Nullable String requestingBeanName) {TargetSource ts = new TargetSource() {@Overridepublic Class<?> getTargetClass() {return element.lookupType;}@Overridepublic boolean isStatic() {return false;}@Overridepublic Object getTarget() {// 从Spring容器中获得beanreturn getResource(element, requestingBeanName);}@Overridepublic void releaseTarget(Object target) {}};// 创建Aop代理ProxyFactory pf = new ProxyFactory();pf.setTargetSource(ts);if (element.lookupType.isInterface()) {pf.addInterface(element.lookupType);}ClassLoader classLoader = (this.beanFactory instanceof ConfigurableBeanFactory ?((ConfigurableBeanFactory) this.beanFactory).getBeanClassLoader() : null);return pf.getProxy(classLoader);
}

从ApplicationContext获取对象

在方法具体使用对象时再去Spring容器中获取对象,这样拿到的就是代理对象:

package com.morris.spring.entity.circular;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;@EnableAsync
public class SingleFieldBeanD implements ApplicationContextAware {private ApplicationContext applicationContext;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}public SingleFieldBeanD getSingleFieldBeanD() {return applicationContext.getBean(SingleFieldBeanD.class);}@Asyncpublic void test() {System.out.println("test");}}

重构方法

重新建class,把@Async的方法放在新的类中,从根本上消除循环依赖。

【spring】解决因@Async引起的循环依赖报错相关推荐

  1. Spring循环依赖报错的处理

    在AService和BService两个循环依赖的时候 @Component public class AService {private BService bService;public AServ ...

  2. Spring IOC 容器源码分析 - 循环依赖的解决办法

    1. 简介 本文,我们来看一下 Spring 是如何解决循环依赖问题的.在本篇文章中,我会首先向大家介绍一下什么是循环依赖.然后,进入源码分析阶段.为了更好的说明 Spring 解决循环依赖的办法,我 ...

  3. 从一个Spring动态代理Bug聊到循环依赖

    文章目录 Bug复现 结论 @PostConstruct的在Bean的生命周期的哪一步 一般代理类的生成时机在生命周期的哪一步 解决办法 两个思路 1.不生成代理类 2.在生成代理类之后再进行数据的初 ...

  4. 解决idea导入项目后依赖报错问题

    解决idea导入项目后依赖报错问题 参考文章: (1)解决idea导入项目后依赖报错问题 (2)https://www.cnblogs.com/dayandday/p/10607195.html (3 ...

  5. npm 安装依赖报错解决方法总结

    npm 安装依赖报错解决方法总结 参考文章: (1)npm 安装依赖报错解决方法总结 (2)https://www.cnblogs.com/ysxq/p/11658571.html (3)https: ...

  6. 解决雷神SpringBoot2中导入依赖报错的问题

    1.导入spring-boot-starter-parent依赖报错的问题 <parent><groupId>org.springframework.boot</grou ...

  7. 解决MySQL事务未提交导致死锁报错 避免死锁的方法

    版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/xuheng8600/article/d ...

  8. ssm启动不报错_解决idea导入ssm项目启动tomcat报错404的问题

    用idea写ssm项目,基于之前一直在用spring boot 对于idea如何运行ssm花费了一番功夫 启动tom act一直在报404 我搜了网上各种解决办法都不行,花费一天多的时间解决不了 就是 ...

  9. webview在android8.0,解决Android8.0系统应用打开webView报错

    由于webView存在安全漏洞,谷歌从5.1开始全面禁止系统应用使用webview,使用会导致应用崩溃错误提示:Caused by: java.lang.UnsupportedOperationExc ...

最新文章

  1. 快速完整的基于点云闭环检测的激光SLAM系统
  2. 清华大学计算机学科顾问委员会第三次会议举行
  3. 系统访问慢的几个原因
  4. nn.Upsampling is deprecated. Use nn.functional.interpolate instead.
  5. 快速记忆python函数-python之格式化字符串速记整理
  6. mysql存储number_DUMP函数--Oracle是如何在内部存储NUMBER类型的数据?
  7. 学习笔记(16):Python网络编程并发编程-开启子进程的两种方式
  8. Codeforces Round #688 (Div. 2)
  9. 齐博php百度编辑器上传图片_php版百度编辑器ueditor怎样给上传图片自动添加水印?...
  10. CUDA C编程权威指南 第六章 流和并发
  11. rendering omni shadow in one pass.
  12. sunplus8202v 无线游戏手柄——续
  13. JavaWeb练习项目--JEE商城
  14. 网页开发者模式调整到手机模式_突破极限?ROG 游戏手机 3 内藏 160Hz 刷新率模式...
  15. LINUX SHELL中大小写转换及注意事项
  16. js实现微信浏览器关闭
  17. AllenNLP 用法总结
  18. uni-app 颜色选择器(插件分享)
  19. Centos7.5系统部署禅道协调管理系统以及配置优化
  20. 模电—初探MOSFET

热门文章

  1. 20 Newsgroups数据集
  2. 中通物流基于 KubeSphere 在生产环境的开发与部署实践
  3. PyQt5基础使用!(二)
  4. 【windows脚本】组策略\关机脚本
  5. 我的世界服务器海岛地图文件,《我的世界》极限海岛地图存档
  6. 好书推荐之我读过的技术书v1
  7. 2021蓝牙耳机排行榜前十:高颜值高性价比TOP10不要错过
  8. 爬取周公解梦数据(一)
  9. javascript模拟点击事件--实现视频自动播放
  10. 电脑花屏是屏幕坏了吗_电脑屏幕花屏问题诊断及解决方案 --总结