美文网首页
MiniMall:还在到处抛异常捕获异常?统一异常处理很有必要

MiniMall:还在到处抛异常捕获异常?统一异常处理很有必要

作者: Anbang713 | 来源:发表于2020-04-24 07:53 被阅读0次

    1. 传统的异常处理

    对于异常的处理,你通常是不是这样的:

    public void save(User entity) throws Exception {
        // xxx
    }
    

    又或者是这样的:

    public void save(User entity) throws Exception {
        try {
            // xxx
        } catch (Exception e) {
            LOGGER.error("发生异常:{}", e.getMessage());
        }
    }
    

    以上两种方法在功能上并没有什么不妥,但是在代码实现上显示地try/catch,实在有点不优雅而且不易维护。而统一异常处理的方案无需在其它可能发生异常的地方捕获异常,而是由统一异常处理类捕获异常,并向前端返回统一规范的响应信息。

    2. 异常处理流程

    我们的项目中对异常的处理使用统一的异常处理流程:

    • 自定义异常类型。
    • 自定义错误代码及错误信息。
    • 对于可预知的异常由程序员在代码中主动抛出,有SpringMVC统一捕获。
    • 对于不可预知的异常(运行时异常)由SpringMVC统一捕获Exception类型的异常。不可预知异常通常由于系统出现bug或一些不可抗拒的错误(比如网络中断、服务器宕机等),异常类型为RuntimeException类型。
    • 可预知的异常及不可预知的运行时异常最终会采用统一的信息格式(错误代码+错误信息)来表示,最终也会随请求响应给客户端。

    2.1 异常处理流程图

    (1)在controller、service、dao中程序员抛出自定义异常;Spring MVC框架抛出框架异常类型。

    (2)统一由异常捕获类捕获异常,并进行处理。

    (3)捕获到自定义异常则直接取出错误代码及错误信息,响应给客户端。

    (4)捕获到非自定义异常类型首先从Map中找该异常类型是否对应具体的错误代码,如果有则取出错误代码和错误信息并响应给客户端,如果从Map中找不到异常类型所对应的错误代码则统一为99999错误代码响应给客户端。

    (5)将错误代码及错误信息以json格式响应给客户端。

    3. 可预知异常处理

    3.1 自定义异常类

    @Getter
    @AllArgsConstructor
    public class MallException extends RuntimeException {
    
        private ResultCode resultCode;
    }
    

    3.2 异常抛出类

    public class MallExceptionCast {
    
        public static void cast(ResultCode resultCode) {
            throw new MallException(resultCode);
        }
    }
    

    3.3 异常捕获类

    使用@RestControllerAdvice@ExceptionHandler注解来捕获指定类型的异常。

    @Slf4j
    @RestControllerAdvice
    public class MallExceptionCatch {
        
        /**
         * 捕获自定义异常
         *
         * @param exception
         * @return
         */
        @ExceptionHandler(MallException.class)
        public ResponseResult customException(MallException exception) {
            // 输出栈顶信息
            exception.printStackTrace();
            // 记录日志
            log.error("捕获到自定义异常:{}", exception.getMessage());
            return new ResponseResult(exception.getResultCode());
        }
    }
    

    4. 不可预知异常处理

    这里我们定义了一个ImmutableMap类型的变量,其key为异常处理类,value为响应操作码。该变量的作用用于对一些可预见的异常做约定的响应操作码响应,比如NullPointException空指针异常。这些可预见的异常,通过静态代码块初始化。具体如下:

    @Slf4j
    @RestControllerAdvice
    public class MallExceptionCatch {
    
        //定义map,配置异常类型所对应的错误代码
        private static ImmutableMap<Class<? extends Throwable>, ResultCode> EXCEPTIONS;
        //定义map的builder对象,去构建ImmutableMap
        protected static ImmutableMap.Builder<Class<? extends Throwable>, ResultCode> builder = ImmutableMap.builder();
    
        /**
         * 捕获自定义异常
         *
         * @param exception
         * @return
         */
        @ExceptionHandler(MallException.class)
        public ResponseResult customException(MallException exception) {
            // 输出栈顶信息
            exception.printStackTrace();
            // 记录日志
            log.error("捕获到自定义异常:{}", exception.getMessage());
            return new ResponseResult(exception.getResultCode());
        }
    
        /**
         * 捕获其它异常
         *
         * @param exception
         * @return
         */
        @ExceptionHandler(Exception.class)
        public ResponseResult exception(Exception exception) {
            exception.printStackTrace();
            //记录日志
            log.error("捕获到异常信息:{}", exception.getMessage());
            if (EXCEPTIONS == null) {
                EXCEPTIONS = builder.build();//EXCEPTIONS构建成功
            }
            //从EXCEPTIONS中找异常类型所对应的错误代码,如果找到了将错误代码响应给用户,如果找不到给用户响应99999异常
            ResultCode resultCode = EXCEPTIONS.get(exception.getClass());
            if (resultCode != null) {
                return new ResponseResult(resultCode);
            } else if (exception instanceof MethodArgumentNotValidException) {
                String errors = getErrorMsgIfMethodArgumentNotValidException((MethodArgumentNotValidException) exception);
                return new ResponseResult(new CustomResultCode(false, "-1", errors));
            } else {
                //返回99999异常
                return new ResponseResult(CommonsResultCode.SERVER_ERROR);
            }
        }
    
        private String getErrorMsgIfMethodArgumentNotValidException(MethodArgumentNotValidException exception) {
            Iterator iterator = exception.getBindingResult().getAllErrors().iterator();
            List<String> errors = new ArrayList<>();
            while (iterator.hasNext()) {
                ObjectError error = (ObjectError) iterator.next();
                errors.add(error.getDefaultMessage());
            }
            return JSONUtil.toJsonStr(errors);
        }
    
        static {
            //定义异常类型所对应的错误代码
            builder.put(HttpMessageNotReadableException.class, CommonsResultCode.INVALID_PARAM);
        }
    }
    

    现在异常已经统一交给MallExceptionCatch处理,那么一开始的伪代码就不再需要显示的try/catch了,只关心业务代码即可。如项目模块的业务层实现类(StoreServiceImpl)中的一段代码:

    @Override
    protected void doBeforeSave(Store entity) {
        super.doBeforeSave(entity);
        // 不允许存在代码重复的项目
        Optional<Store> optional = storeRepository.findByCode(entity.getCode());
        if (optional.isPresent()) {
            if (entity.getUuid() == null || entity.getUuid().equals(optional.get().getUuid()) == false) {
                MallExceptionCast.cast(InvestResultCode.CODE_IS_EXISTS);
            }
            // 如果是已禁用状态,不允许修改
            if (optional.get().getState().equals(UsingState.disabled)) {
                MallExceptionCast.cast(InvestResultCode.ENTITY_IS_DISABLED);
            }
        }
        // 如果是编辑,则代码不允许修改
        if (StringUtils.isNotBlank(entity.getUuid())) {
            Store store = findById(entity.getUuid());
            if (store.getCode().equals(entity.getCode()) == false) {
                MallExceptionCast.cast(InvestResultCode.CODE_IS_NOT_ALLOW_MODIFY);
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:MiniMall:还在到处抛异常捕获异常?统一异常处理很有必要

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