问题来自:Spring事务的传播行为中REQUIRES_NEW真的有效吗
这个是Spring 对拦截的实现有关。Spring 拦截实现的方法是动态生成一个代理类。正常使用 @Autowired 注解注入的实际上就是这个代理类。
一。 对于有接口实现的类代理,Spring 使用的是 Java 自带的代理生成方式。这种方式对 target.method() 方式的调用是可以拦截到的,对于类内调用 method() 方式则拦截不到。
看以下代码
public interface DynamicProxyInterface {
void a();
void b();
}
public class DynamicProxy implements DynamicProxyInterface
{
@Override
public void a() {
System.out.println("this is a");
b();
}
@Override
public void b() {
System.out.println("this is b");
}
public static void main(String[] args) {
DynamicProxy target = new DynamicProxy();
DynamicProxyInterface dynamicProxy = (DynamicProxyInterface) Proxy.newProxyInstance(DynamicProxy.class.getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke in proxy");
return method.invoke(target, args);
}
});
dynamicProxy.a();
}
}
执行结果为:
invoke in proxy
this is a
this is b
从这可以看出你类内自行调用方法是不会被代理拦截到的,因此你使用的事务注解也就不会生效。
二。 对于单纯的class,没有接口,则 Spring 使用 cglib 进行代理,这里 Spring实现了自己的 CallbackFilter,具体类可以参见 Spring 源码CglibAopProxy ,在目标类的invoke方法中,我们可以看到这块代码
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
Object target = this.targetSource.getTarget();//获取原始对象(未被代理)
try {
oldProxy = AopContext.setCurrentProxy(proxy);
Object retVal = methodProxy.invoke(target, args);
return processReturnType(proxy, target, method, retVal);
}
finally {
AopContext.setCurrentProxy(oldProxy);
this.targetSource.releaseTarget(target);
}
}
在第二行,我们看到 Spring 获取当前被代理的对象,直接进行invoke,类内方法也不会被cglib 代理到
我们写一个测试方法来试下,在上面main 方法里最后加入测试代码:
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(DynamicProxy.class);
enhancer.setCallback(new MethodInterceptor() {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("invoke in cglib");
return methodProxy.invoke(target, objects);
}
});
DynamicProxy cglibProxy = (DynamicProxy) enhancer.create();
cglibProxy.a();
}
执行结果(上面是Java proxy 输出结果,与上面一样)
invoke in proxy
this is a
this is b
invoke in cglib
this is a
this is b
Spring 针对这种情况通过 threadlocal 的方式暴露了当前类的代理,可以使用
AopContext.currentProxy();
方式得到,使用获取到的代理类再调用方法就可以再次走事务的处理逻辑了。即
SpringTransactionMyBatisService service = (SpringTransactionMyBatisService)AopContext.currentProxy();
service.saveNew();
最后,如果你使用的 xml 配置,那么需要在 aop 配置中,设置 expose-proxy 为true
以上。