0
点赞
收藏
分享

微信扫一扫

关于 Spring 事务注解 this 调用失效的一个细节


下面这个是一个非常经典的 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

  • ​​再议 MyBatis 中的动态代理​​

关于 Spring 事务注解 this 调用失效的一个细节_代理类


举报

相关推荐

0 条评论