SpringAop报错是否影响@Transactional回滚问题

文章目录

  • 问题起始
    • aop中的执行顺序
    • Spring如何实现Aop?如何通过一个@Transactional注解回滚?
    • 那么现在核心问题来,aop与transactional他们的拦截器,执行顺序又是什么呢?
  • 总结

问题起始

今天被人问了个问题,说SpringAop里面报错,@Transactional事务会进行回滚吗? 当时第一个反应是不会,想法是bean对象实际是获取的一个proxy,@Transactional不会比我们的aop更后面执行吧?但是也不能确定,所以就有了今天这篇文章。

我们先来看看aop里面的各个方法的执行顺序

aop中的执行顺序


/**
 * @Author:TangFenQi
 * @Date:2021/11/4 23:59
 **/
@Aspect
@Component
@Order(1)
public class ExceptionAop {

    /**
     * 定义切入点:对要拦截的方法进行定义与限制,如包、类
     *
     * 1、execution(public * *(..)) 任意的公共方法
     * 2、execution(* set*(..)) 以set开头的所有的方法
     * 3、execution(* per.spring.boot.learning.q1.service.MyService.*(..)),MyService这个类里的所有的方法
     * 4、execution(* per.spring.boot.learning.q1.service.*.*(..)),service包下的所有的类的所有的方法
     * 5、execution(* per.spring.boot.learning.q1.service..*.*(..)),service包及子包下所有的类的所有的方法
     * 6、execution(* per.spring.boot.learning.q1.service..*.*(String,?,Long)) service包及子包下所有的类的有三个参数,第一个参数为String类型,第二个参数为任意类型,第三个参数为Long类型的方法
     */
    @Pointcut("execution(* per.spring.boot.learning.q1.service.MyService.*(..))")
    private void pointCut(){

    }

    /**
     * 方法之前
     */
    @Before("pointCut()")
    private void before(JoinPoint joinPoint){
        System.out.println("before");
    }

    /**
     * 方法执行完毕之后
     */
    @AfterReturning(returning = "returnObject",pointcut = "pointCut()")
    private void afterReturning(JoinPoint joinPoint,Object returnObject){
        //throw new RuntimeException("after running 报错");
        System.out.println("after return");
    }

    /**
     * 方法之后
     */
    @After("pointCut()")
    public void after(JoinPoint joinPoint){
        //throw new RuntimeException("after 报错");
        System.out.println("after");
    }

    /**
     * 方法抛出异常执行
     */
    @AfterThrowing(pointcut = "pointCut()",throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex){
        System.out.println("throwing");
    }

    /**
     * 环绕执行,执行之前,执行之后
     */
    @Around("pointCut()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("around-before");
        proceedingJoinPoint.proceed();
        System.out.println("around-after");
    }
}

输出的执行顺序:

around-before
before
===============
业务方法
===============
after return
after
around-after

Spring如何实现Aop?如何通过一个@Transactional注解回滚?

这里看似是两个问题,实际上问的是一个问题,为什么这么说呢?

  • Aop的作用是什么?在方法前后执行执行一段我们插入的代码对吧,比如日志,比如鉴权等。
  • @Transactional的作用呢?如果出现异常帮助我们回滚事务对吧,那他是一段代码吗?

这其实都是在动态代理下的不同实现而已,本质上也就是不同的Interceptor,我们结合一个图来看看.

  1. Aop中的执行流程
AopBefore方法
业务代码
AopAfter方法
  1. @Transactional中
正常
异常
开启事务
业务代码
commit
rollback

所以就像上所描述的一样,本质我们搞清楚了,那么两者之间的关系就是平级关系。

而我们将Bean托管给Spring的容器管理,我们从中获取的对象,其实是一个增强类,或者说是代理类。
SpringAop报错是否影响@Transactional回滚问题_第1张图片

这里我们的Service是MyService,实际拿到的是spring通过cglib动态生成的MyServiceProxy,当我们访问方法时,就会进过下面的一系列Interceptor(0-6),其中我们要看到就是第一个DynamicAdvisedInterceptor,类如其名,动态通知拦截器里面就有我们的主角。

那么现在核心问题来,aop与transactional他们的拦截器,执行顺序又是什么呢?

进入DynamicAdvisedInterceptor类的intercept方法看下(只需要注意中文注释的地方),

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);
				//只需要注意这里
				//这里就是获取到所有的Advice,也就是aop 里面定义的方法或者 transactional的实现。
				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...
					//这里就是将上面获得的所有Advice集合(chain变量)放入其中,并开始访问
					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);
				}
			}
		}

下图是我一些advice
在这里插入图片描述
而这里面第一个就是TransactionInterceptor帮助我们提交或回滚事务的Interceptor,
而我自己定义的Aop切面类,在他的后面。

在运行process()方法(同样只需要注意中文注释的地方)


@Override
	@Nullable
	public Object proceed() throws Throwable {
		// We start with an index of -1 and increment early.
		//interceptorsAndDynamicMethodMatchers就是我们的chain集合
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}
        //按照索引从0到1逐渐取出拦截器执行
		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);
		}
	}

从中可以得知,执行顺序就是chain集合的放入顺序。

所以按照上面的理解,执行顺序就是

ExposeInvocationInterceptor不在本章之内
TransactionInterceptor
Aop-Around
MethondBeforeAdviceInterceptor
Aop-After
Aop-AfterReturning
Aop-AfterThrowing

这里有个小细节啊,可能有些同学发现,不是说after在afterReturning之后执行吗?在chain中怎么还在前面啊,这是应为after是后置方法啊,如果前置方法当然是排在前面的先执行,而对于后置方法反而是先执行的后运行(因为有process()方法),这可能有点绕,需要同学稍微理解一下。

所以从这个源码中也与我们实际运行的结果相互印证,那么问个问题,如果在afterReturning中报错抛出异常,那么afterThrowing是否能捕获?

按照上述理解的原理,是不可能捕获的到的,因为AfterThrowing在chain的最后,反而是最先执行完毕的,所以他对于上层是无法感知的。

下图(只是描述后置方法的情况,前置就是按顺序嘛,好理解,后置需要反过来)就是类的包裹情况(理解为wrapper好点),下层是无法感知到上层的情况的

SpringAop报错是否影响@Transactional回滚问题_第2张图片
回到最开始的问题,SpringAop抛出异常是否会导致@Transactional注解失效?其实不会…当然本质不是因为不会,而是我们默认的加载情况下TransactionInterceptor会比我们的Aop先加载进行,如果我们能调整他的顺序,那么StringAop切面当然会影响到@Transactional注解啊。
那么我们怎么去调整顺序呢?
通过spring的 @Order注解,越小越先执行


@Aspect
@Component
@Order(1)
public class ExceptionAop {
    ...
}

然后在看下chain的排序

SpringAop报错是否影响@Transactional回滚问题_第3张图片

总结

方法访问的流程

proxy
chains-before
business-method
chains-after

其中chains就是一个扩展点,他的不同实现会提供不同的功能,比如说日志记录,事务回滚等一些非业务的通用代码,理解了这个对我们实际应用spring会有很大的帮助。

到此,本章结束。

代码无涯,与君共勉。

项目地址:github
喜欢的同学,不介意的话给我点个star吧 (* ̄︶ ̄)

你可能感兴趣的:(spring系列,spring,java,后端)