同时使用 @Transactional和@Async出现的报错

问题场景

在上一篇文章中spring中autowired注入自己的代理后,最后容器中的对象是原对象还是代理对象,我们谈到了在spring中autowired注入自己的代理后,最后容器中实际只有一个根据事务切面生成的代理对象。

现在我们给文章中涉及的代码加@Async注解

@Service
public class UserRightService {

  	@Autowired
    private UserRightService userRightService;

    @Transactional
    @Async
    public boolean checkUserFuncRight(String userId, String url) {
		......
		......
    }
}

然而启动项目报错

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'userRightService': Bean with name 'userRightService' has been injected into other beans [userRightService] 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:624)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
	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:321)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1304)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1224)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640)
	... 42 common frames omitted

分析

我们结合spring源码一步步分析:为什么加了@Async注解后,反而报错了?

1.populateBean方法注入成员

该方法给UserRightService类注入成员变量UserRightService后,spring也会往二级缓存中存放一份。

2.initializeBean方法

之后进入initializeBean方法,

在这里插入图片描述

方法遍历里BeanPostProcessor,如下
在这里插入图片描述

由于加了@Async注解,此时会遍历到AsyncAnnotationBeanPostProcessor的父类AbstractAdvisingBeanPostProcessor的如下方法,
在这里插入图片描述
红框处会生成代理对象。

此时,initializeBean方法返回的exposedObject是一个通过AsyncAnnotationBeanPostProcessor生成的新代理对象。

3.获取二级缓存数据

由于二级缓存已经存放了数据,所以下面if (earlySingletonReference != null) {为true,之后触发下图红框的判断逻辑

在这里插入图片描述
那到底进入哪一个条件分支呢?

因为exposedObject是一个通过AsyncAnnotationBeanPostProcessor生成的新代理对象,而bean是原对象,所以第一个红框if不成立;
所以,进入第二个红框的else if,最终抛出文章开头的异常。

如此设计的原因

spring为什么要这么设计?假如spring最后不报错,出现的场景是:

假设我在A类也注入了UserRightService成员,那么最终A类的UserRightService成员,和UserRightService类自身注入的UserRightService成员,就不是同一个代理对象了,违反了单例原则。

解决方式

通过分析可知,if (earlySingletonReference != null) {为true的条件是二级缓存能获取到数据,所以我们可以避免从二级缓存获取到数据,来让条件为false。
而二级缓存能获取到数据的前提是该类触发了循环依赖,即通过@Autowired注入自己,或者A->B->A模式。
所以,避免循环依赖,就可以同时使用 @Transactional@Async
但如果我们确实需要在UserRightService类的方法里用到自身代理对象,可以在代码中通过AopContext.currentProxy()来获取。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值