专栏首页用户7614879的专栏Java 使用ControllerAdvice进行全局异常处理以及全局统一返回值处理

Java 使用ControllerAdvice进行全局异常处理以及全局统一返回值处理

在springboot应用开发中,面对程序可能出现的各项异常,最好有一个全局的处理。

不然假设后端因为某些原因抛出异常,比如空指针,文件不存在等,会直接返回500

在前后端分离的项目中,前端会拿到internal server error + 后端的一大堆异常堆栈。这对前端是毫无用处的。

使用controlleradvice + exceptionhandler 可以实现后端应用的全局异常处理。

另外还可以通过自定义异常,在需要的时候抛出异常,交给全局异常处理器来返回某些逻辑;通过编码可以实现全局异常处理器对不同类型的异常执行不同的逻辑。

这里我针对一些我自定义的异常,返回特殊提示信息,并对spring的validation产生的各项异常,提取其中前端需要的信息做为message字段返回。

除了使用spring的validation自动校验参数,有时可能需要程序中动态校验来弥补validation不能完成的逻辑,若不符合验证条件就抛出paramerrorexception

另外在业务逻辑中出现一些返回值仅代表成功运行的函数,无法通过返回值区分成功失败的情况,就可以手动抛出tipexception来交给异常处理器处理返回。

@Slf4j
@ControllerAdvice
public class TipControllerAdvice {

    /**
     * 全局异常处理
     */
    @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);
    }

}

以上仅为异常处理部分

在大部分前后端分离项目中,后端的返回值基本都需要包装成一个ResponseVo,其中属性有code、message、data等,来供前端使用区分。

这样就导致大部分controller写完后都需要手动构建一个responseVo对象并填充属性返回,也就造成了大量的重复代码。

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

同样有responseBodyAdvice,就有requestBodyAdvice。

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

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

这里我使用responseBodyAdvice这个注解后,在每一个controller只需要返回需要的data 或者true/false 等,交由spring为我封装好统一返回值返回给前端。

另外还判断了404的情况,针对前端访问了一个后端不存在的接口地址,返回提示信息而不是404状态码。

完整代码:

/**
 * 统一响应处理器
 * 1 在每个responseBody的响应返回之前进行处理
 * 2 全局异常捕捉 统一返回格式
 *
 * @author wyh
 * @date 2020/11/30 17:39
 **/
@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;
    }
}

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

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

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

若返回结果为boolean 则交由responseVo的构造方法,为true则返回success + 0,false 则返回failure + -1 。可根据业务需要随意扩展即可

项目中有了这么个东西,代码写起来就很舒服了。

@RestController
@RequestMapping(Constants.URL_PREFIX + "/system")
public class SystemController {

    @Autowired
    private SystemService systemService;

    /**
     * 获取https开关
     */
    @GetMapping("/setting_https")
    public HttpsSettingVo getHttpsSetting() {
        return HttpsSettingVo.builder().web(ConfigConstants.HTTPS_FLAG).build();
    }


}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 《Springboot极简教程》系统异常全局统一处理:@ControllerAdvice plus @ExceptionHandler统一异常处理代码实例运行

    用@ControllerAdvice和@ExceptionHandler两个注解来做异常的统一处理。

    一个会写诗的程序员
  • SpringBoot统一异常处理BasicErrorController定义全局异常处理类:并用@ControllerAdvice注解自定义异常类,继承Exception(或RuntimeExcept

    JavaEdge
  • Spring Boot2 系列教程(十二)@ControllerAdvice 的三种使用场景

    严格来说,本文并不算是 Spring Boot 中的知识点,但是很多学过 SpringMVC 的小伙伴,对于 @ControllerAdvice 却并不熟悉,S...

    江南一点雨
  • 深入理解Spring异常处理

    相信我们每个人在SpringMVC开发中,都遇到这样的问题:当我们的代码正常运行时,返回的数据是我们预期格式,比如json或xml形式,但是一旦出现了异常(比如...

    宜信技术学院
  • Spring Boot实现分布式微服务开发实战系列(六)

    上一篇文章讲了Redis缓存的安全防范及Kafka的接入及消息实现,今天接着前面的内容基础说说项目的优化和基础配置,今天要讲的内容主要是Spring Boot项...

    攻城狮的那点事
  • Spring 中的统一异常处理

    在具体的SSM项目开发中,由于Controller层为处于请求处理的最顶层,再往上就是框架代码的。 因此,肯定需要在Controller捕获所有异常,并且做适当...

    芋道源码
  • Spring Boot 学习三:静态资源、整合 Thymeleaf 页面模板、@RestControllerAdvice

    在 Spring Boot 中,默认情况下,一共有5个位置可以放静态资源,五个路径分别是如下:

    关忆北.
  • 掌握 Spring 之异常处理

    这次我们学习 Spring 的异常处理,作为一个 Spring 为基础框架的 Web 程序,如果不对程序中出现的异常进行适当的处理比如异常信息友好化,记录异常日...

    闻人的技术博客
  • springboot项目自定义统一异常处理

    异常指的是在程序运行过程中发生的异常事件,通常是由外部问题(如硬件错误、输入错误)所导致的。在Java等面向对象的编程语言中异常属于对象

    许喜朝
  • 第二十一章:SpringBoot项目中的全局异常处理

    恒宇少年
  • Spring Boot2.x-11 使用@ControllerAdvice和@ExceptionHandler实现自定义全局异常

    我们在Spring Boot2.x-07Spring Boot2.1.2整合Mybatis这边文章的基础上来实现下Spring Boot使用@Controlle...

    小小工匠
  • SpringMVC 中 @ControllerAdvice 注解的三种使用场景!

    @ControllerAdvice ,很多初学者可能都没有听说过这个注解,实际上,这是一个非常有用的注解,顾名思义,这是一个增强的 Controller。使用这...

    江南一点雨
  • 还在try...catch?如果是那你就out了!

    前言 一、需要用到的注解 二、全局异常捕获代码实现  1.创建GlobalExceptionHandler.java  2.增加抛出异常DemoControll...

    main方法
  • spring-boot-route(四)全局异常处理

    在开发中,我们经常会使用try/catch块来捕获异常进行处理,如果有些代码中忘记捕获异常或者不可见的一些异常出现,就会响应给前端一些不友好的提示,这时候我们可...

    Java旅途
  • springboot 统一异常处理(包含统一数据校验)

    (1)只要没有成功,不管什么原因,前端界面给出提示:服务端错误/异常。哪怕是数据校验不过,也这样提示(嗯,反正先把锅甩出去再说,具体什么原因我才不在乎呢,老子就...

    java架构师
  • Spring Boot简明教程--全局异常处理

    为了统一开发过程中的异常处理方式和返回值,需要为项目制定统一的全局异常处理。在SpringBoot中全局异常处理通过@ControllerAdvice注解以及 ...

    听城
  • Spring 异常处理的各种姿势

    统一的异常处理对于应用的重要性不言而喻。今天我们来介绍一下 Spring 如何来进行统一的 Rest 异常处理。同时我们也会简单比较一下它们之间的优劣。

    码农小胖哥
  • SpringBoot图文教程15—项目异常怎么办?「跳转404错误页面」「全局异常捕获」

    异常处理在Java中是一种很常规的操作,在代码中我们常用的方法是try catch或者上抛异常。

    鹿老师的Java笔记
  • SpringBoot 入门——局部与全局的异常处理

    一、构建测试代码 1 新建MAVEN项目 打开IDE—新建Maven项目—构建一个简单Maven项目 ? ? ? 2 编写pom.xml引入包 编写pom配置引...

    企鹅号小编

扫码关注云+社区

领取腾讯云代金券