@Transactional
或 @Async
注解失效原因很简单:它们的原理都是动态代理,也就是说只有通过代理类去调用方法才能够使方法有"增强“的效果。这里以 @Async
为例。
下面定义一个 Service
:两个异步执行的方法 test03()
和 test02()
用来模拟项目中可能出现的耗时的操作,然后 test()
方法调用这两个耗时的方法:
定义 Controller
:
执行方法,返回结果:
方法执行结果明显与我们的预期不符,方法的输出顺序表示了 test02()
和 test03()
两个异步方法居然同步执行了,也就是说 @Aysnc
注解失效了!
失效的原因是因为我们是在
test()
方法中直接调用的test02()
和test03()
方法,相当于是this.test02()
和this.test03()
调用的,也就是说真正调用test02()
和test03()
方法的是TestService
对象本身调用的,而@Async
和@Transactional
注解本质使用的是动态代理,真正应该是TestService
的代理对象调用test02()
和test03()
方法。其实 Spring 容器在初始化的时候 Spring 容器会将含有 AOP 注解的类对象“替换”为代理对象(简单这么理解),那么注解失效的原因就很明显了,就是因为调用方法的是对象本身而不是代理对象,因为没有经过Spring 容器,那么解决方法也会沿着这个思路来解决。
网上有不少博客说解决方法就是将要异步执行的方法单独抽取成一个类,这样的确可以解决异步注解失效的问题,原理就是当你把执行异步的方法单独抽取成一个类的时候,这个类肯定是被 Spring 管理的,其他 Spring 组件需要调用的时候肯定会注入进去,这时候实际上注入进去的就是代理类了,其实还有其他的解决方法,并不一定非要单独抽取成一个类。
解决方式一:在 TestService 中通过上下文获取自己的代理对象调用异步方法
其实我们的注入对象都是从 Spring 容器中给当前 Spring 组件进行成员变量的赋值,由于 TestService
使用了AOP注解,那么实际上 TestService
在 Spring 容器中实际存在的是它的代理对象。
(工具类可以参考:
https://blog.csdn.net/Dongguabai/article/details/80788646 )
执行结果,异步方法异步执行了:
. 解决方式二:开启 CGLIB 代理,手动获取 Spring 代理类
在 Spring Boot 启动类上加上:
使用 AopContext.currentProxy()
获取当前代理类:
这里为了证明 Spring 容器中的对象就是当前代理类对象特地输出了一句话:
运行结果:
OK,问题完美解决!