zoukankan      html  css  js  c++  java
  • 浅析Java如何使用@ControllerAdvice、@ExceptionHandler进行全局统一异常处理、如何使用@responseBodyAdvice进行全局统一返回值处理

    一、统一异常处理

    1、统一异常处理的 2 个注解

      系统有一个统一异常处理的功能,可减少重复代码,又便于维护。用@ControllerAdvice和@ExceptionHandler两个注解来做异常的统一处理。

    @ControllerAdvice:作用于所有@Controller标注的Controller类

    @ExceptionHandler:作用于所有@RequestMapping标注的方法抛出的指定类型的异常

    2、统一异常处理代码示例

    @Slf4j
    @ControllerAdvice
    public class TipControllerAdvice {
        // 全局异常处理
        @ResponseBody
        @ExceptionHandler(value = Exception.class)
        public ResponseVo<String> handler(Exception e) {
            String msg = "系统内部出错";
            log.error(msg, e);
            return ResponseVo.failure(msg);
        }
        // 参数校验异常异常处理
        @ResponseBody
        @ExceptionHandler(value = ConstraintViolationException.class)
        public ResponseVo<String> handlerConstraintViolationException(Exception e) {
            ConstraintViolationException constraintViolationException = (ConstraintViolationException) e;
            String msg = StringUtils.collectionToCommaDelimitedString(
                    constraintViolationException.getConstraintViolations()
                            .stream()
                            .map(ConstraintViolation::getMessage)
                            .collect(Collectors.toList()));
            return ResponseVo.failure(msg);
        }
        ......
    }

    二、统一返回值处理

    1、@responseBodyAdvice 注解

      在大部分前后端分离项目中,后端的返回值基本都需要包装成一个ResponseVo,其中属性有code、message、data等,来供前端使用区分。这样就导致大部分controller写完后都需要手动构建一个responseVo对象并填充属性返回,也就造成了大量的重复代码。

      这类代码其实有很方便的处理方式,就是使用spring提供的注解 responseBodyAdvice,同样有 responseBodyAdvice,就有 requestBodyAdvice。

    requestBodyAdvice ——  请求体的统一处理器,一般用来对请求参数做一些统一的解密等。

    responseBodyAdvice ——  响应体的统一处理器,一般用来统一返回值使用。

      这里我使用 responseBodyAdvice 这个注解后,在每一个 controller 只需要返回需要的 data 或者 true/false 等,交由spring为我封装好统一返回值返回给前端。另外还判断了404的情况,针对前端访问了一个后端不存在的接口地址,返回提示信息而不是404状态码。

    2、示例代码:

    /**
     * 统一响应处理器
     * 1 在每个responseBody的响应返回之前进行处理
     * 2 全局异常捕捉 统一返回格式
     **/
    @Slf4j
    @ControllerAdvice
    public class TipControllerAdvice implements ResponseBodyAdvice<Object> {
    
        private static final Integer STATUS_404 = 404;
        public static final String ERROR_MSG_404 = "接口地址不存在";
    
        // 决定是否执行beforeBodyWrite()方法
        @Override
        public boolean supports(MethodParameter methodParameter, Class aClass) {
            return true;
        }
    
        @SneakyThrows
        @Override
        public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
            if (o == null) {
                return ResponseVo.failure();
            }
            //String类型需要特殊处理 手动转为json字符串
            if (o instanceof String) {
                return JsonUtil.toJson(ResponseVo.success(o));
            }
            if (o instanceof ResponseVo) {
                return o;
            }
            //boolean类型 返回对应的成功或失败
            if (o instanceof Boolean) {
                return ResponseVo.builder((Boolean) o);
            }
            //404时 返回特定信息
            if (is404(o)) {
                return ResponseVo.failure(ERROR_MSG_404);
            }
            return ResponseVo.success(o);
        }
        // 全局异常处理
        @ResponseBody
        @ExceptionHandler(value = Exception.class)
        public ResponseVo<String> handler(Exception e) {
            //default error message
            String msg = "系统内部出错";
            log.error(msg, e);
            return ResponseVo.failure(msg);
        }
        // 参数校验异常异常处理
        @ResponseBody
        @ExceptionHandler(value = ConstraintViolationException.class)
        public ResponseVo<String> handlerConstraintViolationException(Exception e) {
            ConstraintViolationException constraintViolationException = (ConstraintViolationException) e;
            String msg = StringUtils.collectionToCommaDelimitedString(
                    constraintViolationException.getConstraintViolations()
                            .stream()
                            .map(ConstraintViolation::getMessage)
                            .collect(Collectors.toList()));
            return ResponseVo.failure(msg);
        }
        @ResponseBody
        @ExceptionHandler(value = MethodArgumentNotValidException.class)
        public ResponseVo<String> handlerMethodArgumentNotValidException(Exception e) {
            StringBuilder message = new StringBuilder();
            MethodArgumentNotValidException exception = (MethodArgumentNotValidException) e;
            List<ObjectError> errors = exception.getBindingResult().getAllErrors();
            for (ObjectError objectError : errors) {
                if (objectError instanceof FieldError) {
                    FieldError fieldError = (FieldError) objectError;
                    message.append(StrUtil.toUnderlineCase(fieldError.getField())).append(":").append(fieldError.getDefaultMessage()).append(",");
                } else {
                    message.append(objectError.getDefaultMessage()).append(",");
                }
            }
            return ResponseVo.failure(message.toString());
        }
        @ResponseBody
        @ExceptionHandler(value = BindException.class)
        public ResponseVo<String> handlerBindException(Exception e) {
            BindException bindException = (BindException) e;
            String msg = StringUtils.collectionToCommaDelimitedString(
                    bindException.getAllErrors()
                            .stream()
                            .map(DefaultMessageSourceResolvable::getDefaultMessage)
                            .collect(Collectors.toList()));
            return ResponseVo.failure(msg);
        }
        @ResponseBody
        @ExceptionHandler(value = MissingServletRequestParameterException.class)
        public ResponseVo<String> handlerMissingServletRequestParameterException(Exception e) {
            return ResponseVo.failure("缺少必填参数");
        }
        @ResponseBody
        @ExceptionHandler(value = HttpMessageNotReadableException.class)
        public ResponseVo<String> handlerHttpMessageNotReadableException(Exception e) {
            return ResponseVo.failure("请求参数异常");
        }
        @ResponseBody
        @ExceptionHandler(value = ParamErrorException.class)
        public ResponseVo<String> handlerParamError(Exception e) {
            if (StrUtil.isBlank(e.getMessage())) {
                return ResponseVo.failure("参数错误");
            } else {
                return ResponseVo.failure(e);
            }
        }
        @ResponseBody
        @ExceptionHandler(value = TipException.class)
        public ResponseVo<String> handlerTip(Exception e) {
            return ResponseVo.failure(e);
        }
        private boolean is404(Object o) {
            if (o instanceof Map) {
                Map<String, Object> map = Convert.toMap(String.class, Object.class, o);
                Integer status = Convert.toInt(map.get("status"));
                return STATUS_404.equals(status);
            }
            return false;
        }
    }

    (1)根据supports方法可以动态决定是否需要执行下面的beforeBodyWrite方法,返回false就不会执行了。

    (2)为了满足有些接口还是会返回responseVo的情况,加了层判断,若返回的类已经是responseVo了就直接返回,不进行任何包装。

    (3)这里为string类型做了特殊处理,需要手动转一下json,不然会报错。

      这个情况具体可以看这篇博客:实现ResponseBodyAdvice接口,统一拦截接口返回数据时,controller返回值是String 类型时异常  ——  https://blog.csdn.net/lrt890424/article/details/83627554

      这里摘录一下:

      3.1、报错信息:java.lang.ClassCastException: com.lk.face.common.model.ResponseDataVo cannot be cast to java.lang.String

      3.2、解决办法:在ResponseBodyAdvice中对String 类型做单独判断

    (4)若返回结果为boolean 则交由responseVo的构造方法,可根据业务需要随意扩展即可。

      以上来源于这篇文章的学习:https://cloud.tencent.com/developer/article/1769559

  • 相关阅读:
    Notes about "Exploring Expect"
    Reuse Sonar Checkstyle Violation Report for Custom Data Analysis
    Eclipse带参数调试的方法
    MIT Scheme Development on Ubuntu
    Manage Historical Snapshots in Sonarqube
    U盘自动弹出脚本
    hg的常用配置
    Java程序员的推荐阅读书籍
    使用shared memory 计算矩阵乘法 (其实并没有加速多少)
    CUDA 笔记
  • 原文地址:https://www.cnblogs.com/goloving/p/15044590.html
Copyright © 2011-2022 走看看