• SpringMVC项目整合SSM统一异常处理


    1. 问题描述

    先来演示个效果,修改UserController类的getById方法

        @GetMapping("/{userId}")
        public Result findByUserId(@PathVariable Integer userId){
            //手动添加一个错误信息
            if(userId==10){
                int i = 10/0;
            }
            User user = userService.findByUserId(userId);
            if (user != null){
                return new Result(user,ResultCode.SELECT_SUCCES,"查询成功");
            }
            return new Result(null,ResultCode.SELECT_FAIL,"查询失败");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    重新启动运行项目,使用PostMan发送请求,当传入的id10,则会出现如下效果:

    在这里插入图片描述

    前端接收到这个信息后和之前我们约定的格式不一致,这个问题该如何解决?

    在解决问题之前,我们先来看下异常的种类及出现异常的原因:

    • 框架内部抛出的异常:因使用不合规导致
    • 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
    • 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
    • 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
    • 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)

    看完上面这些出现异常的位置,你会发现,在我们开发的任何一个位置都有可能出现异常,而且这些异常是不能避免的。所以我们就得将异常进行处理。

    思考

    1. 各个层级均出现异常,异常处理代码书写在哪一层?

      所有的异常均抛出到表现层进行处理

    2. 异常的种类很多,表现层如何将所有的异常都处理到呢?

      异常分类

    3. 表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决?

      AOP

    对于上面这些问题及解决方案,SpringMVC已经为我们提供了一套解决方案:

    • 异常处理器:

      • 集中的、统一的处理项目中出现的异常。

        在这里插入图片描述

    2. 异常处理器的使用

    2.1 环境准备

    • 创建一个Web的Maven项目
    • pom.xml添加SSM整合所需jar包
    • 创建对应的配置类
    • 编写Controller、Service接口、Service实现类、Dao接口和模型类
    • resources下提供jdbc.properties配置文件

    最终创建好的项目结构如下:

    在这里插入图片描述

    2.2 使用步骤

    步骤1:创建异常处理器类

    //@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
    @RestControllerAdvice
    public class ExceptionAdvice {
        //除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
        @ExceptionHandler(Exception.class)
        public void doException(Exception ex){
            System.out.println("异常!!! 发现一个异常!!!");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    确保SpringMvcConfig能够扫描到异常处理器类

    步骤2:让程序抛出异常

    修改UserControllerfindByUserId方法,添加int i = 10/0;

        @GetMapping("/{userId}")
        public Result findByUserId(@PathVariable Integer userId){
            //手动添加一个错误信息
            if(userId==10){
                int i = 10/0;
            }
            User user = userService.findByUserId(userId);
            if (user != null){
                return new Result(user,ResultCode.SELECT_SUCCES,"查询成功");
            }
            return new Result(null,ResultCode.SELECT_FAIL,"查询失败");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    步骤3:运行程序,测试

    在这里插入图片描述

    说明异常已经被拦截并执行了doException方法。

    异常处理器类返回结果给前端

    //@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
    @RestControllerAdvice
    public class ExceptionAdvice {
        //除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
        @ExceptionHandler(Exception.class)
        public Result doException(Exception ex){
            System.out.println("异常!!! 发现一个异常!!!");
            return new Result("异常错误",-999,"异常!!! 发现一个异常!!!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    启动运行程序,测试

    在这里插入图片描述

    至此,就算后台执行的过程中抛出异常,最终也能按照我们和前端约定好的格式返回给前端。

    知识点1:@RestControllerAdvice

    名称@RestControllerAdvice
    类型类注解
    位置Rest风格开发的控制器增强类定义上方
    作用为Rest风格开发的控制器类做增强

    **说明:**此注解自带@ResponseBody注解与@Component注解,具备对应的功能

    在这里插入图片描述

    知识点2:@ExceptionHandler

    名称@ExceptionHandler
    类型方法注解
    位置专用于异常处理的控制器方法上方
    作用设置指定异常的处理方案,功能等同于控制器方法,
    出现异常后终止原始控制器执行,并转入当前方法执行

    **说明:**此类方法可以根据处理的异常不同,制作多个方法分别处理对应的异常

    3. 项目异常处理方案

    3.1 异常分类

    异常处理器我们已经能够使用了,那么在咱们的项目中该如何来处理异常呢?

    因为异常的种类有很多,如果每一个异常都对应一个@ExceptionHandler,那得写多少个方法来处理各自的异常,所以我们在处理异常之前,需要对异常进行一个分类:

    • 业务异常(BusinessException)

      • 规范的用户行为产生的异常

        • 用户在页面输入内容的时候未按照指定格式进行数据填写,如在用户名输入的是null

          在这里插入图片描述

      • 不规范的用户行为操作产生的异常

        • 如用户故意传递错误数据

          在这里插入图片描述

    • 系统异常(SystemException)

      • 项目运行过程中可预计但无法避免的异常
        • 比如数据库或服务器宕机
    • 其他异常(Exception)

      • 编程人员未预期到的异常,如:用到的文件不存在

        在这里插入图片描述

    将异常分类以后,针对不同类型的异常,要提供具体的解决方案:

    3.2 异常解决方案

    • 业务异常(BusinessException)
      • 发送对应消息传递给用户,提醒规范操作
        • 大家常见的就是提示用户名已存在或密码格式不正确等
    • 系统异常(SystemException)
      • 发送固定消息传递给用户,安抚用户
        • 系统繁忙,请稍后再试
        • 系统正在维护升级,请稍后再试
        • 系统出问题,请联系系统管理员等
      • 发送特定消息给运维人员,提醒维护
        • 可以发送短信、邮箱或者是公司内部通信软件
      • 记录日志
        • 发消息和记录日志对用户来说是不可见的,属于后台程序
    • 其他异常(Exception)
      • 发送固定消息传递给用户,安抚用户
      • 发送特定消息给编程人员,提醒维护(纳入预期范围内)
        • 一般是程序没有考虑全,比如未做非空校验等
      • 记录日志

    3.3 异常解决方案的具体实现

    思路:

    1.先通过自定义异常,完成BusinessException和SystemException的定义

    2.将其他异常包装成自定义异常类型

    3.在异常处理器类中对不同的异常进行处理

    步骤1:自定义异常类

    //自定义异常处理器,用于封装异常信息,对异常进行分类
    public class SystemException extends RuntimeException {
        private Integer code;
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public SystemException(Integer code, String message) {
            super(message);
            this.code = code;
        }
    
        public SystemException(Integer code, String message, Throwable cause) {
            super(message, cause);
            this.code = code;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    //自定义异常处理器,用于封装异常信息,对异常进行分类
    public class BusinessException  extends RuntimeException {
        private Integer code;
    
        public Integer getCode() {
            return code;
        }
    
        public void setCode(Integer code) {
            this.code = code;
        }
    
        public BusinessException(Integer code, String message) {
            super(message);
            this.code = code;
        }
    
        public BusinessException(Integer code, String message, Throwable cause) {
            super(message, cause);
            this.code = code;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    说明:

    • 让自定义异常类继承RuntimeException的好处是,后期在抛出这两个异常的时候,就不用在try…catch…或throws了
    • 自定义异常类中添加code属性的原因是为了更好的区分异常是来自哪个业务的

    步骤2:将其他异常包成自定义异常

    假如在UserServiceImpl的findByUserId方法抛异常了,该如何来包装呢?

        public User findByUserId(Integer userId) {
            //模拟业务异常,包装成自定义异常
            if(userId == 10){
                throw new BusinessException(ResultCode.BUSINESS_ERR,"请按要求请求参数!");
            }
            //模拟系统异常,将可能出现的异常进行包装,转换成自定义异常
            try{
                int i = 20/0;
            }catch (Exception e){
                throw new SystemException(ResultCode.SYSTEM_TIMEOUT_ERR,"服务器访问超时,请重试!",e);
            }
            
            return userDao.findByUserId(userId);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    具体的包装方式有:

    • 方式一:try{}catch(){}在catch中重新throw我们自定义异常即可。
    • 方式二:直接throw自定义异常即可

    上面为了使code看着更专业些,我们在Code类中再新增需要的属性

    //状态码
    public class ResultCode {
        // 成功 状态
        public static final Integer SAVE_SUCCES = 200; //保存成功
        public static final Integer UPDATE_SUCCES = 200; //修改成功
        public static final Integer DELETE_SUCCES = 200; //删除成功
        public static final Integer SELECT_SUCCES = 200; //查询成功
    
        // 失败
        public static final Integer SAVE_FAIL = 2001;//保存失败
        public static final Integer UPDATE_FAIL = 2002;//修改失败
        public static final Integer DELETE_FAIL = 2003;//删除失败
        public static final Integer SELECT_FAIL = 2004;//查询失败
    
        // 其他 ......
        public static final Integer BUSINESS_ERR = -999;
        public static final Integer SYSTEM_TIMEOUT_ERR = -888;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    步骤3:处理器类中处理自定义异常

    //@RestControllerAdvice用于标识当前类为REST风格对应的异常处理器
    @RestControllerAdvice
    public class ExceptionAdvice {
        //除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
    //    @ExceptionHandler(Exception.class)
    //    public Result doException(Exception ex){
    //        System.out.println("异常!!! 发现一个异常!!!");
    //        return new Result("异常错误",-999,"异常!!! 发现一个异常!!!");
    //    }
    
    
        //@ExceptionHandler用于设置当前处理器类对应的异常类型
        @ExceptionHandler(SystemException.class)
        public Result doSystemException(SystemException ex){
            //记录日志
            //发送消息给运维
            //发送邮件给开发人员,ex对象发送给开发人员
            return new Result(ex.getCode(),null,ex.getMessage());
        }
    
        @ExceptionHandler(BusinessException.class)
        public Result doBusinessException(BusinessException ex){
            return new Result(ex.getCode(),null,ex.getMessage());
        }
    
        //除了自定义的异常处理器,保留对Exception类型的异常处理,用于处理非预期的异常
        @ExceptionHandler(Exception.class)
        public Result doOtherException(Exception ex){
            //记录日志
            //发送消息给运维
            //发送邮件给开发人员,ex对象发送给开发人员
            return new Result(ResultCode.SYSTEM_UNKNOW_ERR,null,"系统繁忙,请稍后再试!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    步骤4:运行程序

    根据userId查询,

    如果传入的参数为10,会报BusinessException

    在这里插入图片描述

    如果传入的是其他参数,会报SystemException

    在这里插入图片描述

    对于异常我们就已经处理完成了,不管后台哪一层抛出异常,都会以我们与前端约定好的方式进行返回,前端只需要把信息获取到,根据返回的正确与否来展示不同的内容即可。

    小结

    以后项目中的异常处理方式为:

    在这里插入图片描述


    项目代码

  • 相关阅读:
    力扣--找不同
    【Java 基础篇】Java字节打印流详解:处理二进制数据的利器
    神经网络(七)优化与正则化
    python基础--函数的应用、lambda表达式以及高阶函数
    查看已退出docker容器的日志
    什么是广电入库
    C++设计模式-单例模式,反汇编
    初识微服务技术栈
    Codeforces Round #792 (Div. 1 + Div. 2)
    Mysql——》索引存储模型推演
  • 原文地址:https://blog.csdn.net/qq_37726813/article/details/127780447