SpringBoot之全局异常处理

作者: 若丨寒 | 来源:发表于2019-04-23 22:26 被阅读23次

    异常处理问题分析

    异常如何处理

    问题引入

    • 针对代码中的异常,常规有两种处理方式,一种throws直接抛出,另一种try..catch捕获。
    • 在java项目中,有可能存在人为逻辑的异常,也可能为取得异常的详情,或是保证程序在异常时继续向下执行,会采用第二种处理方式。
    • 但是,代码中每一处异常都来捕获,会使代码什么冗余且不利于维护。
      解决思路
    • 定义一个全局异常处理类,返回统一规范的异常信息;
    • 处理逻辑是,先判定是否会出现异常,再执行后续具体的业务。

    业务举例

    本文主要为了实现全局异常处理的逻辑,只举简单业务

    某公司部门需增加员工,处理流程:1先根据员工编号查询员工对象,2判断员工对象是否有信息,即是否不为空,3若有信息,则说明已存在,无需再添加,若不是,则直接添加。

    代码如下:

    public class MyService {
        // 注入dao层
        @Autowired
        EmployeeecMapper employeeecMapper;
    
        /**
         * 添加员工信息
         * @param employee 员工对象
         * @return 影响的行数
         */
        public int add(Employee employee) {
            // 根据id查询员工对象
            Employeeec emp = employeeecMapper.selectByPrimaryKey(employee.getId());
            // 判断是否已有该员工
            if (emp != null){
                // 已有,抛出异常,异常信息为已有该员工
                throw new RuntimeException("异常代码:1201,错误信息:该员工已存在");
            }
            // 没有,插入该员工
            return employeeecMapper.insert(emp);
        }
    }
    

    异常处理流程

    业务中存在运行时异常和业务逻辑异常,前者不运行时很难察觉,后者在遍及业务时就可以定义出来,因此异常分为不可预知异常和可知异常。流程如下:

    1. 自定义全局异常类,使用@ControllerAdvice,控制器增强
    2. 自定义错误代码及错误信息,两种异常最终会采用统一的信息格式来表示,错误代码+错误信息。
    3. 对于可预知的异常由程序员在代码中主动抛出,由SpringMVC统一捕获。
    4. 不可预知异常通常是由于系统出现bug、或一些外界因素(如网络波动、服务器宕机等),异常类型为RuntimeException类型(运行时异常)。

    可知异常

    定义异常信息类,变量为错误代码和错误信息,捕获自定义异常时,直接将该对象返回

    不可知异常

    定义一个map,将常见的异常存入其中,并定义错误代码。对于其他不常见的异常,即map中没有的,同一一个异常对象返回即可。

    异常处理代码流程

    可知异常

    1、定义打印异常信息与返回结果的接口

    public interface ResultCode {
        // 操作是否成功
        boolean success();
    
        // 操作结果代码
        long code();
    
        // 提示信息
        String message();
    }
    
    public interface Response {
        public static final boolean SUCCESS = true;
        public static final int SUCCESS_CODE = 10000;
    }
    

    2、定义打印异常信息的枚举类和返回结果类

    @ToString
    public enum  CommonCode implements ResultCode {
        NO_PAGE(false,404,"没有信息"),
        FAIL(false,500,"操作失败!"),
        SUCCESS(true,200,"操作成功!");
    
        // 结果信息
        boolean success;
        long code;
        String message;
        
        // 带参构造
        CommonCode(boolean success, long code, String message) {
            this.success = success;
            this.code = code;
            this.message = message;
        }
    
        @Override
        public boolean success() {
            return true;
        }
    
        @Override
        public long code() {
            return code;
        }
    
        @Override
        public String message() {
            return message;
        }
    }
    
    @Data
    @ToString
    public class ResponseResult implements Response {
    
        boolean success = SUCCESS;
    
        long code = SUCCESS_CODE;
    
        String message;
    
        public ResponseResult(ResultCode resultCode){
            this.success = resultCode.success();
            this.code = resultCode.code();
            this.message = resultCode.message();
        }
    }
    

    3、定义错误异常类

    public class CustomException extends RuntimeException{
    
        @Autowired
        ResultCode resultCode;
        
        // 带参构造
        public CustomException(ResultCode resultCode){
            this.resultCode = resultCode;
        }
        
        // getter
        public ResultCode getResultCode(){
            return resultCode;
        }
    }
    

    4、定义异常抛出类

    public class ExceptionCast {
    
        // 静态方法
        public static void cast(ResultCode resultCode){
            throw new CustomException(resultCode);
        }
    }
    

    5、定义异常捕获类,使用ControllerAdvice控制器增强的注解,并在捕获CustomException异常的方法上加ExceptionHandler注解,即可捕获该类的所有异常,返回json数据。

    @ControllerAdvice 
    public class ExceptionCatch {
    
        /**
         * 捕获CustomException类异常
         * @param customException
         * @return 结果信息,json数据
         */
        @ExceptionHandler(CustomException.class)
        @ResponseBody
        public ResponseResult customException(CustomException customException){
            ResultCode resultCode = customException.getResultCode();
            return new ResponseResult(resultCode);
        }
    }
    

    6、在业务中抛出异常

    public class MyService {
    
        @Autowired
        EmployeeecMapper employeeecMapper;
    
        public int add(Employee employee) {
            Employeeec emp = employeeecMapper.selectByPrimaryKey(employee.getId());
            if (emp != null){
                ExceptionCast.cast(CommonCode.FAIL);
            }
            return employeeecMapper.insert(emp);
        }
    }
    

    不可知异常处理

    1、类似可知异常,先在CommonCode类中添加错误代码,如

    UNAUTHORISE(false,510,"没有权限"),
    

    2、在异常捕获类中添加不可知异常的捕获方法。该方法中,定义一个只读的map存储异常类型的错误代码的映射,map中没有的元素,统一用错误代码999来定义。

    UNKNOWNERROR(false,999,"未知异常"),
    
    @ControllerAdvice
    public class ExceptionCatch {
    
        // 定义map,存贮常见错误信息。该类map不可修改
        private static ImmutableMap<Class<? extends Throwable>,ResultCode> EXCEPTIONS;
        // 构建ImmutableMap
        protected static ImmutableMap.Builder<Class<? extends Throwable>,ResultCode> builder = ImmutableMap.builder();
    
        @ExceptionHandler(CustomException.class)
        @ResponseBody
        public ResponseResult customException(CustomException customException){
            ResultCode resultCode = customException.getResultCode();
            return new ResponseResult(resultCode);
        }
    
        /**
         * 捕获非自定义类异常
         * @param exception
         * @return
         */
        @ExceptionHandler(Exception.class)
        @ResponseBody
        public ResponseResult exception(Exception exception){
            // 记录日志
            LOGGER.error("catch exception ==> ",exception.getMessage());
            if (EXCEPTIONS == null){
                EXCEPTIONS = builder.build();
            }
            ResultCode resultCode = EXCEPTIONS.get(exception.getClass());
            if (resultCode != null){
                return new ResponseResult(resultCode);
            }else {
                return new ResponseResult(CommonCode.UNKNOWNERROR);
            }
        }
    
        static {
            builder.put(HttpMessageNotReadableException.class, CommonCode.INVALID_PARAM);
        }
    }
    

    读者分享

    在这给大家推荐一个微信公众号,那里每天都会有技术干货、技术动向、职业生涯、行业热点、职场趣事等一切有关于程序员的内容分享。更有海量Java架构、移动互联网架构相关源码视频,面试资料,电子书籍截止于4月28日免费发放。我看了觉得资源还不错,如果你们有需要的话,扫描下方二维码关注wx公众号免费获取↓↓↓


    资源大本营↓↓↓

    Java架构资料
    Java源码解析,到各种框架学习,再到项目实战,一应俱全,包括但不限于:Spring、Mybatis等源码、Java进阶、Java架构师、虚拟机、性能优化、并发编程、数据结构和算法。

    Java架构视频资料
    Java架构视频资料
    java架构面试专题资料

    移动互联网架构资料
    Android进阶、Android架构、APP开发、NDK模块开发、小程序开发、微信开发。

    Android架构资料

    相关文章

      网友评论

        本文标题:SpringBoot之全局异常处理

        本文链接:https://www.haomeiwen.com/subject/zvhigqtx.html