事务注解@transactional和异步调用注解@Async失效了——AOP原理解析

650 阅读2分钟

背景介绍

在使用@Async注解时,发现在本类中调用的方法异步注解失效了,实际上还是同步调用,后定位是Spring的AOP实现方式,导致注解没有生效。

动态代理两种方式

Spring实现动态代理有两种方式,JDK代理和CGLIB代理。

使用JDK还是CGLIB

1)当Bean实现接口时,Spring就会用JDK的动态代理。
2)当Bean没有实现接口时,Spring使用CGlib是实现。
3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。

JDK代理和CGLIB代理实现原理

可见我的另一篇博客 JDK代理和CGLIB代理

代码详解

接口:

interface  Demo{
  int add(int, int);
  int addOne(int);
}

实现类:

class Real implements Demo {
  @Override
  public int add(int x, int y) {
  	int res = x + y + this.addOne();
    return res;
  }
  
  @Override
  @Async
  public int addOne(int x) {
  	Thread.sleep(2000);
    return x + 1;
  }
}

这里我们在add方法中调用本类的addOne方法。

我们引出jdk代理的实现(与CGLIB类似)

public ProxyClass implements Demo {
  private static Method mAdd;
  private static Method mAddOne;

  private InvocationHandler handler;

  static {
    Class clazz = Class.forName("Demo");
    mAdd = clazz.getMethod("add", int.class, int.class);
    mAddOne = clazz.getMethod("addOne", int.class);
  }
  
  @Override
  public int add(int x, int y) {
    return (Integer)handler.invoke(this, mAdd, new Object[] {x, y});
  }
    
  @Override
  public int addOne(int x) {
    return (Integer)handler.invoke(this, mAddOne, new Object[] {x});
  }
}

这里我们在代理类中实际上是通过反射来调用add方法的,这样如果调用代理类的add方法,实际上是调用了InvocationHandler中的invoke方法,如下:

class Handler implements InvocationHandler {
  private final Real real;

  public Handler(Real real) {
    this.real = real;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args)
      throws IllegalAccessException, IllegalArgumentException,
             InvocationTargetException {
    System.out.println("=== BEFORE ===");
    Object re = method.invoke(real, args);
    System.out.println("=== AFTER ===");
    return re;
  }
}

可以看到切面的内容是定义在invoke方法中,而我们实际通过反射调用的add方法是原本Real实例中的add方法,因此Real实例中的this就是Real实例本身,而不是代理类,所以最后调用的this.addOne()方法,不会做异步处理。
因此我们会发现Thread.sleep(2000);是同步执行的,并没有发生异步行为。

总结

碰到使用AOP实现的注解时,我们要避免自调用,如@transactional和@Async注解。
如果一定要自调用,应该用如下写法:

((Real) AopContext.currentProxy()).addOne();