下面这个是一个非常经典的 Spring 事务注解失效的例子:
@Serevice
public class TestService {
@Resource
private TestMapper testMapper;
@Transactional
public void method1() {
int re = testMapper.insert();
int a = 1/0;
}
public void method2(){
this.method1(); //method1 事务失效
}
}
我们分析原因的时候都会提一句:由于是 this
调用造成事务注解失效。这个说法本身没有问题,但还是没有描述清楚一个细节。
我们先想一下这个调用过程,比如是一个 TestController
来调用这个 TestService
,那么本质其实是 TestController
调用的 TestService
的代理类 TestService$Proxy
,那么代理类其中的 method1()
方法就是一个代理方法。那么 TestController
调用 TestService#method2
-> this#method1
的时候,这个 this
不是代理类 TestService$Proxy
的实例嘛,前面已经说了,代理类 TestService$Proxy
的 method1()
方法就是一个代理方法,那此时 this
(即代理类 TestService$Proxy
实例)调用的 method1()
就是一个代理方法。既然是代理方法,理应事务要生效的。
关于这个问题我们可以从两个角度分析。首先从源码角度进行分析,抓大放小,既然是分析 AOP 相关的,那么肯定是看 org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptor#intercept
:
@Override
@Nullable
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
//目标对象
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
//获取拦截器链
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
//如果拦截器链是空的 ,且是 public 方法
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
//可以看到就是一个直接调用
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// We need to create a method invocation...
//要使用 invocation
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
继续看 org.springframework.aop.framework.ReflectiveMethodInvocation#proceed
方法:
@Override
@Nullable
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
要注意这是一个拦截器链,当最后一个拦截器执行完成后就会执行 org.springframework.aop.framework.ReflectiveMethodInvocation#invokeJoinpoint
-> org.springframework.aop.support.AopUtils#invokeJoinpointUsingReflection
方法:
// target是目标对象
@Nullable
public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
throws Throwable {
// Use reflection to invoke the method.
try {
ReflectionUtils.makeAccessible(method);
//执行的目标对象的方法
return method.invoke(target, args);
}
catch (InvocationTargetException ex) {
// Invoked method threw a checked exception.
// We must rethrow it. The client won't see the interceptor.
throw ex.getTargetException();
}
catch (IllegalArgumentException ex) {
throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
method + "] on target [" + target + "]", ex);
}
catch (IllegalAccessException ex) {
throw new AopInvocationException("Could not access method [" + method + "]", ex);
}
}
到这里其实就已经水落石出了。虽然 Spring 搞了很多拦截器去增强目标方法,但实际执行被代理方法的时候还是靠目标对象去执行的(这也可以说明代理类是会持有被代理类的引用的),所以此时的 this
并不是代理对象,而是被代理对象,所以事务注解也就失效了。
上面主要是通过源码分析的角度来说明为啥 this
调用会造成事务注解失效。因为目标方法实际就是被代理对象执行的,所以这个 this
就是被代理对象。但是代理对象毕竟还是显得有点看不见摸不着,接下来结合《再议 MyBatis 中的动态代理》,看下生成的代理对象(JDK 的动态代理,虽然与 CGLib 有区别,但是在本文的议题中是不影响的)长啥样:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import com.example.myBatisDemo.proxy.ITarget;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements ITarget {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final void show() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
...
}
可以看到生成的代理类 $Proxy0
实现了 ITarget
接口,实际执行 show()
方法的时候,会执行 invoke()
方法,也就是 JdkProxyBuilder
(细节见《再议 MyBatis 中的动态代理》)的 invoke()
方法,然后 JdkProxyBuilder
又持有目标对象的引用,通过反射执行了目标对象的方法。
也就是说本质上这个目标方法还是被目标对象执行的,代理类只是包了一层。所以用 this
调用去解释文章开头 Spring 事务注解失效的场景是没毛病的,只是这样描述会容易引起歧义。
References
欢迎关注公众号: