这篇笔记主要记录spring事务失效场景三:在同一个类中,用非事务方法调用事务方法
首先,这种场景,失效的原因是,在非事务方法中,调用同一个类的事务方法,和动态代理没有关系,事务拦截器无法拦截到,就是一个this方法调用。所以,在实际业务开发过程中,一定要避免这种场景
应用
应用其实简单,就是这样的,在非事务方法中,调用了同类中的一个事务方法
下面这里是debug的结果
可以看到,在进行调用的时候,完全没有被事务拦截器所拦截到,是因为非事务方法testNoTrans(),所以,这里就是一个普通的方法的调用,导致这样的原因是:在cglibAopProxy在拦截到目标方法之后,会根据method,判断当前method所要经过的通知方法有哪些,但是非事务方法,所需要执行的通知方法是空,所以,就直接进行了目标方法的调用,也就是说
1.如果在A.class类中的某些方法,添加了事务注解,那spring是会为该类生成代理对象的
2.在调用该类中的非事务方法的时候,也是会被代理对象所拦截,只是说,如果当前方法所需要执行的通知方法是空,就直接去调用目标方法了
3.如果调用的方法是事务方法,则会被事务拦截器所拦截到,去进行事务的处理
不生效的原理
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 {
/**
* 这里和exposeProxy这个属性设置有关系,如果在@EnableTransactionManagemenr
* 注解中设置了exposeProxy属性为true,这里就会设置到threadLocal中一个proxy对象
* 在业务代码中,就可以通过AopContext.getCurrentProxy()获取到一个代理对象
*/
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);
/**
* 获取目标方法执行时,需要调用的通知方法,然后将对应的advice转换为拦截器放到chain中
* 如果chain为空,就直接调用目标方法,否则就通过aop的形式,调用所有的通知方法
*/
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())) {
// 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...
/**
* 这里采用的是责任链模式
*/
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);
}
}
}
这里的源码,可以看到,在根据方法获取到所有要执行的通知方法之后,会进行一层判断,如果要执行的通知方法为空,就直接调用目标方法
所以,getInterceptorsAndDynamicInterceptionAdvice是核心方法,会根据当前method,去判断当前method所需要经过的通知方法,前面也说过,动态代理对象是针对类的,只要一个类中有添加事务注解,那就会为其生成代理对象,但是在实际调用的时候,会根据method,进行一层判断,只有加了事务注解的方法,才会被拦截器拦截到
如果在非事务方法中,调用事务方法,其实就是一个普通方法的调用,并不会被拦截器拦截到