这周开发自测刚好遇到了使用@Transactional和@Async的不生效的问题,参考网上资料后,发现这篇文章图文并茂,讲的非常清晰易懂,简单做了些补充搬运至此。
实现AOP的方法有动态代理、编译期,类加载期织入等等,Spring实现AOP的方法则就是利用了动态代理机制,正因如此,才会导致某些情况下@Async和@Transactional不生效。
@EnableAsync //添加此注解开启异步调用 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
当某些任务执行时间较长,且客户端不需要及时获取结果(如调用第三方API),只要在需要异步调用的任务上添加 @Async
即可,如:
@Async //可以@Async("")指定自定义线程池(beanName) void asyncTask(String keyword) { try { // 模拟处理过程 Thread.sleep(5000); } catch (InterruptedException e) { //logger //error tracking } System.out.println(keyword); }
这样asyncTask
就会异步执行。 然而,如果在同一个Class内(比如业务逻辑同在一个XXXServiceImpl),出现下面这样的情况,先调用一个非异步任务
private void noAsyncTask(String keyword){ asyncTask(keyword); //该方法内再调用异步方法 } @Async void asyncTask(String keyword) { try { Thread.sleep(5000); } catch (InterruptedException e) { //logger //error tracking } System.out.println(keyword); }
此时,@Async是没有生效的。 原因就是@Async和@Transaction利用了动态代理机制。
当Spring发现@Transactional或者@Async时,会自动生成一个ProxyObject,如:
此时调用Class.transactionTask会调用ProxyClass.产生事务操作。
然而当Class里的一个非事务方法调用了事务方法,ProxyClass是这样的:
到这里应该可以看明白了,如果调用了noTransactionTask方法,最终会调用到Class.transactionTask,而这个方法是不带有任何Transactional的信息的,也就是@Transactional根本没有生效哦。
简单来说就是: 同一个类内这样调用的话,只有第一次调用了动态代理生成的ProxyClass,之后一直用的是不带任何切面信息的方法本身。
知道了原因,处理方法也特别简单,就是让noTransactionTask里依旧调用ProxyClass的transactionTask方法:只需要显示利用Spring暴露的AopContext即可。代码如下:
private void noAsyncTask(String keyword){ // 注意这里 调用了代理类的方法, 或者直接在spring上下文获取bean(如果有aop,单例池中存放的是织入aop的代理类) ((YourClass) AopContext.currentProxy()).asyncTask(keyword);
// SpringContextUtil.getBean(YourClass.class).asyncTask(keyword);
} @Async void asyncTask(String keyword) { try { Thread.sleep(5000); } catch (InterruptedException e) { //logger //error tracking } System.out.println(keyword); }
如果使用AopContext记得要在Class上加上@EnableAspectJAutoProxy(exposeProxy = true)
来暴露AOP的Proxy对象才行,否则会报错。
或者就可以把这样的方法放到另外一个类里,不要产生类里一个非异步/非事务方法,调用了异步/事务方法,不过大家协同开发同一个文件的话,谁能保证没有人这样调用呢?总而言之无论什么方案,都是使得调用ProxyObject的方法。
参考原文
作者:陶源0111
链接:https://www.jianshu.com/p/9a0de6577ed7