背景介绍
在使用@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();