IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> spring Aop切面的环绕通知时,如何防止切面异常影响主业务流程执行 -> 正文阅读

[Java知识库]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;
    }
}

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-06-01 15:02:20  更:2022-06-01 15:05:27 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:21:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码