spring Aop切面的环绕通知时,如何防止切面异常影响主业务流程执行

一、前言

我们在做切面编程时,可能会使用到的环绕通知@Around。示例代码如下:

@Aspect
@Component
public class MyAspect {
    @Around("execution(public int com.heygo.spring.aop.CalcServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object retValue = null;
        System.out.println("我是环绕通知之前AAA");
        retValue = proceedingJoinPoint.proceed();
        System.out.println("我是环绕通知之后BBB");
        return retValue;
    }
}

使用环绕通知时,我们需要考虑几个问题:

  1. 假设方法执行前的切面代码出现了异常,会导致主业务方法不执行,这种情况如何处理?
  2. 假设方法执行后的切面代码出现了异常,可能会导致主业务方法事务回滚,这种情况怎么处理?
  3. 现要求,如果是切面代码抛出的异常,都直接捕获catch后打印不抛出;而主业务代码出现异常需要抛出去,由系统的统一异常进行处理;现问题是,如何区分切面异常和主业务异常?
  4. 在满足上述的情况下,又如何确保主业务方法只执行一次?

上述情况,是本人工作中遇到的,换做你,你又该如何处理呢。。。下面我说一下我的解决方案

二、场景复现

@Aspect
@Component
public class MyAspect {

    @Pointcut("@annotation(com.junjie.aop.AnnotationLog)")
    public void aspect(){

    }

    @Around(value = "aspect()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = handler(joinPoint);
        return result;
    }

    //业务很复杂的方法,这里省略...
    private Object handler(ProceedingJoinPoint joinPoint) throws Throwable {

        /*
            方法执行前,这里省略一大堆前面代码
            含有一段异常代码:
            int i = 10/0;
         */

        Object result = joinPoint.proceed();

        /*
            方法执行后,这里省略一大堆前面代码
            含有一段异常代码:
            int i = 10/0;
         */

        //放行主业务方法
        return result;
    }
}

这个切面若是执行,肯定会出现异常并且影响到了主业务异常的执行。

三、解决方案

3.1 方案一

直接对handler方法进行catch,异常捕获,代码如下:

  @Around(value = "aspect()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = null;

        try {
            result = handler(joinPoint);

        }catch (Exception e){
            //处理掉异常e,不抛出,代码省略

            //再执行主方法,进行放行
            result = joinPoint.proceed();
        }
        return result;
    }

显然,上述代码,成功的捕获了方法执行前的切面异常,并放行了主业务方法,也达到了,切面代码异常不影响主业务正常执行流程。

假设,我们改写一下handler方法:


	@Around(value = "aspect()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = null;

        try {
            result = handler(joinPoint);

        }catch (Exception e){
            //处理掉异常e,不抛出,代码省略

            //再执行主方法,进行放行
            result = joinPoint.proceed();
        }
        return result;
    }

	//业务很复杂的方法,这里省略...
    private Object handler(ProceedingJoinPoint joinPoint) throws Throwable {

        /*
            方法执行前,这里省略一大堆前面代码
            此处的代码全部正常,不会抛异常了
         */

        Object result = joinPoint.proceed();

        /*
            方法执行后,这里省略一大堆前面代码
            含有一段异常代码:
            int i = 10/0;
         */

        //放行主业务方法
        return result;
    }

分析一下代码,我们发现此方案存在以下缺陷:

  1. 若handler中执行joinPoint.proceed();时出现异常,即主业务异常,也被我们的catch住了,并做了处理,紧接着又再执行一次joinPoint.proceed();放行。这里不仅catch了主业务逻辑异常,而且还执行了两次主业务方法。
  2. 若是方法执行前不再抛出异常了,而方法执行后会抛出异常,这时会执行catch里头的代码,joinPoint.proceed();被执行了两次。

3.2 方案二

思路:
引入一个代理类,代理执行主业务方法,并在这个代理类做一些限制处理,代码如下:

切面代码:

@Aspect
@Component
public class MyAspect {

    @Pointcut("@annotation(com.junjie.aop.AnnotationLog)")
    public void aspect(){

    }

    @Around(value = "aspect()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        JoinPointProxy pointProxy = new JoinPointProxy(joinPoint);

        Object result = null;
        try {
            result = handler(pointProxy);

        }catch (Exception e){
            //主业务逻辑异常,直接抛出,不捕获不处理
            if (pointProxy.isExecuted()){
                Exception exception = pointProxy.getException();
                if (exception != null){
                    throw exception;
                }
            }

            //切面代码异常,捕获并进行处理
            LoggerUtils.error("切面异常",e);

            //确保主业务方法已执行,并放行
            if (pointProxy.isExecuted()){
                return pointProxy.getResult();
            }else {
                return pointProxy.proceed();
            }
        }
        return result;
    }

    //业务很复杂的方法,这里省略...
    private Object handler(JoinPointProxy pointProxy) throws Throwable {

        /*
            方法执行前,这里省略一大堆前面代码
            此处的代码全部正常,不会抛异常了
         */

        Object result = pointProxy.proceed();

        /*
            方法执行后,这里省略一大堆前面代码
            含有一段异常代码:
            int i = 10/0;
         */

        //放行主业务方法
        return result;
    }
}

代理类:

public class JoinPointProxy {

    //方法是否已执行
    private boolean executed;

    //方法执行结果
    private Object result;

    //方法执行异常
    private Exception exception;

    //连接点
    private ProceedingJoinPoint joinPoint;

    public JoinPointProxy(ProceedingJoinPoint joinPoint) {
        this.joinPoint = joinPoint;
    }

    public Object proceed() throws Throwable {
        if (!isExecuted()){
            try {
                result = joinPoint.proceed();
            }catch (Exception e){
                exception = e;
                throw e;
            }finally {
                executed = true;
            }
        }
        return result;
    }

    public boolean isExecuted() {
        return executed;
    }

    public Object getResult() {
        return result;
    }

    public Exception getException() {
        return exception;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值