云迈博客

您现在的位置是:首页 > 后端开发 > Java > 正文

Java

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

黄昊2022-09-05Java252
文章目录问题起始aop中的执行顺序Spring如何实现Aop?如何通过一个@Transactional注解回滚?那么现在核心问题来,aop与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,我们结合一个图来看看.

Aop中的执行流程

@Transactional

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

而我们将Bean托管给Spring的容器管理,我们从中获取的对象,其实是一个增强类,或者说是代理类。

这里我们的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注解失效?其实不会…当然本质不是因为不会,而是我们默认的加载情况下TransactionInterceptor会比我们的Aop先加载进行,如果我们能调整他的顺序,那么StringAop切面当然会影响到@Transactional注解啊。
那么我们怎么去调整顺序呢?
通过spring的 @Order注解,越小越先执行

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

然后在看下chain的排序

总结
方法访问的流程

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

到此,本章结束。

代码无涯,与君共勉。

发表评论

评论列表

  • 这篇文章还没有收到评论,赶紧来抢沙发吧~