• Spring Boot学习笔记————使用@RestControllerAdvice注解统一处理controller中的返回结果和异常


    一、统一处理返回结果

    统一返回值类型无论项目前后端是否分离都是非常必要的,方便对接接口的开发人员更加清晰地知道这个接口的调用是否成功(不能仅仅简单地看返回值是否为 null 就判断成功与否,因为有些接口的设计就是如此)。
    返回结果类:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Result<T> {
    
        private int code;
        private String message;
        private T data;
    
        public static <T> Result<T> success(T data) {
            return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);
        }
    
        public static <T> Result<T> success(String message, T data) {
            return new Result<>(ResultEnum.SUCCESS.getCode(), message, data);
        }
    
        public static Result<?> failed() {
            return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null);
        }
    
        public static Result<?> failed(String message) {
            return new Result<>(ResultEnum.COMMON_FAILED.getCode(), message, null);
        }
    
        public static Result<?> failed(ResultEnum enumEx){
            return new Result<>(enumEx.getCode(),enumEx.getMessage(),null);
        }
    
        public static Result<?> failed(ResultEnum enumEx,String message){
            return new Result<>(enumEx.getCode(),message,null);
        }
        public static Result<?> failed(BaseException ex){
            return new Result<>(ex.getCode(),ex.getMsg(),null);
        }
    
        public static <T> Result<T> instance(Integer code, String message, T data) {
            Result<T> result = new Result<>();
            result.setCode(code);
            result.setMessage(message);
            result.setData(data);
            return result;
        }
    }
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    枚举类父接口

    public interface IResponseEnum {
    
        public int getCode();
    
        public String getMessage();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    基础异常类:

    public abstract class BaseException extends RuntimeException{
    
        private static final long serialVersionUID = 1L;
    
        private int code;
    
        private String msg;
    
    
        public BaseException(int code,String msg){
            super(msg);
            this.code = code;
            this.msg = msg;
        }
    
        public BaseException(IResponseEnum exEnum){
            super(exEnum.getMessage());
            this.code = exEnum.getCode();
            this.msg = exEnum.getMessage();
        }
    
        public BaseException(int code,String msg,Throwable cause){
            super(msg,cause);
            this.code = code;
            this.msg = msg;
        }
    
        public BaseException(IResponseEnum exEnum,Throwable cause){
            super(exEnum.getMessage(),cause);
            this.code = exEnum.getCode();
            this.msg = exEnum.getMessage();
        }
    
        public BaseException(int code,String msg,Throwable cause,
                             boolean enableSuppression,
                             boolean writableStackTrace){
            super(msg,cause,enableSuppression,writableStackTrace);
            this.code = code;
            this.msg = msg;
        }
    
        public BaseException(IResponseEnum exEnum,Throwable cause,
                             boolean enableSuppression,
                             boolean writableStackTrace){
            super(exEnum.getMessage(),cause,enableSuppression,writableStackTrace);
            this.code = exEnum.getCode();
            this.msg = exEnum.getMessage();
        }
        public int getCode() {
            return code;
        }
    
        public void setCode(int code) {
            this.code = code;
        }
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(String msg) {
            this.msg = msg;
        }
    }
    
    • 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
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    常用返回结果枚举类

    public enum ResultEnum implements IResponseEnum {
        SUCCESS(2001,"接口调用成功!"),
        COMMON_FAILED(2001, "接口调用失败"),
     
    
        private int code;
        private String message;
    
        ResultEnum(int code, String message) {
            this.code = code;
            this.message = message;
        }
    
        public int getCode() {
            return code;
        }
    
        public String getMessage() {
            return message;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    定义好统一的返回结果类型后,就可以在controller中使用了,但是如果controller中每个方法结尾都写一段构建返回结果的操作,这些都是很重复的工作,所以还要继续想办法进一步处理统一返回结构。

    Spring 中提供了一个接口 ResponseBodyAdvice 以及@RestControllerAdvice注解,能帮助我们实现上述需求:

    public interface ResponseBodyAdvice<T> {
        boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
    
        @Nullable
        T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ResponseBodyAdvice 是对 Controller 返回的内容在 HttpMessageConverter 进行类型转换之前拦截,进行相应的处理操作后,再将结果返回给客户端。但是只拦截加了@RequestMapping和@ResponseBody的方法的返回结果,这点要注意。

    supports:判断是否要交给 beforeBodyWrite 方法执行,ture:需要;false:不需要
    beforeBodyWrite:对 response 进行具体的处理

    ResponseAdvice 示例:

    @RestControllerAdvice(basePackages="com.example.demo.controller")
    public class ResponseAdvice implements ResponseBodyAdvice<Object> {
        @Override
        public boolean supports(MethodParameter returnType, Class converterType) {
            return true;
        }
    
    
        @Override
        public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
            if (body instanceof Result) {
                return body;
            }else if(body instanceof String){
                try {
                    return new ObjectMapper().writeValueAsString(Result.success(body));
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                }
            }else if(body instanceof Throwable){
                return Result.failed(ResultEnum.COMMON_FAILED,"接口调用出错,请联系管理员!");
            }
            return Result.success(body);
        }
    }
    
    
    • 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

    注意事项:
    1、如果controller方法中返回的是String类型,但是加了@ResponseBody注解,那么在ResponseBodyAdvice中拦截到String类型并且处理完后需要最后返回一个String类型(可以转换成json字符串),否则会报错。

    二、统一异常处理

    业务异常类:

    public class BusinessException extends BaseException {
    
        private static final long serialVersionUID = 1L;
        public BusinessException(int code, String msg) {
            super(code, msg);
        }
    
        public BusinessException(IResponseEnum exEnum) {
            super(exEnum);
        }
    
        public BusinessException(int code, String msg, Throwable cause) {
            super(code, msg, cause);
        }
    
        public BusinessException(IResponseEnum exEnum, Throwable cause) {
            super(exEnum, cause);
        }
    
        public BusinessException(int code, String msg, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(code, msg, cause, enableSuppression, writableStackTrace);
        }
    
        public BusinessException(IResponseEnum exEnum, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(exEnum, cause, enableSuppression, writableStackTrace);
        }
    }
    
    • 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

    业务异常枚举类:

    public enum BusinessExceptionEnum implements IResponseEnum {
        VALIDATE_FAILED(3001, "参数校验失败"),
        USERNOTFOUND(3002,"用户不存在");
    
        private int code;
        private String message;
    
    
        BusinessExceptionEnum(int code, String message){
            this.code = code;
            this.message = message;
        }
        @Override
        public int getCode() {
            return this.code;
        }
    
        @Override
        public String getMessage() {
            return this.message;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    定义好异常类以及对于的异常枚举类,以后新增的异常类型,就只需要在枚举类中新增即可,无需再新增异常类。
    定义好异常类后,就可以使用了,使用@ExceptionHandler和@RestControllerAdvice就可以拦截异常了。

    @RestControllerAdvice
    public class ExceptionAdvice {
    
        @ExceptionHandler(BusinessException.class)
        public Result handleBusinessException(BusinessException e){
            return Result.failed(e);
        }
    
    	//返回错误页面
        @ExceptionHandler(RuntimeException.class)
        public ModelAndView handleError(RuntimeException e){
            ModelAndView modelAndView = new ModelAndView();
            modelAndView.setViewName("error");
            modelAndView.addObject("code", 500);
            modelAndView.addObject("msg", "服务器异常!");
            return modelAndView;
        }
    
        @ExceptionHandler(Exception.class)
       
        public Throwable handleException(Exception e){
            return e;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    controller方法:

    @RequestMapping("/testException")
        public String testException() throws Exception {
            //throw new Exception();
            throw new RuntimeException();
            //throw new BusinessException(BusinessExceptionEnum.USERNOTFOUND);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在 @ExceptionHandler中定义好拦截的异常类型即可拦截指定的异常了。使用@RestControllerAdvice注解表示拦截Controller里的@RequestMapping方法。
    注意事项:
    1、@RestControllerAdvice是在@ControllerAdvice的基础上加上了@ResponseBody,表明加了该注解的方法返回类型是@ResponseBody。所以这里的返回结果也会被ResponseBodyAdvice拦截,所以在ResponseBodyAdvice中也需要统一处理出现异常后的返回结果,参考上文ResponseAdvice类代码。如果使用@ControllerAdvice注解,但是不加上@ResponseBody,则不会被拦截
    2、在单个Controller中也可以定义@ExceptionHandler方法作为本Controller的异常处理方法。优先级是,Controller中的异常处理方法>全局的异常处理方法。定义的处理异常类型越详细优先级越高。比如:如果出现RuntimeException,那么它会被处理RuntimeException异常的方法拦截,不会被处理Exception异常的方法拦截。但是如果处理Exception异常的方法定义在它自己的Controller中,那么它只会被本Controller中的异常拦截方法拦截。

  • 相关阅读:
    项目如何实现富文本框文件上传
    GBase 8s资源管理
    Apache Jmeter BeanShell 实现跨文件跨线程全自动获取Token并写入CSV文件
    广告掘金全自动挂机项目,单设备30+【软件脚本+技术教程】
    简单错误记录-OJ
    灵魂一问:一个Java文件的执行全部过程你确定都清楚吗?
    idea 永久配置 provided scope to classpath
    Python2.7 pip安装tensorflow-1.15
    无效问题已结题自删除
    【JavaEE】_HTTP响应
  • 原文地址:https://blog.csdn.net/qq_34609889/article/details/126300790