Springboot 全局异常处理+统一后端响应(附源码)

前言

本文主要介绍SpringBoot前后端分离开发模式下,如何友好与前端进行交互。包含controller层统一返回处理,全局异常捕获。

统一后端响应

定义返回格式

复制代码
 1 @Getter
 2 @AllArgsConstructor
 3 @NoArgsConstructor
 4 public class ResultVo {
 5 
 6     private int code;
 7 
 8     private String msg;
 9 
10     private Object data;
11 
12 
13     // 默认返回成功状态码,数据对象
14     public ResultVo(Object data) {
15         this.code = ResultCode.SUCCESS.getCode();
16         this.msg = ResultCode.SUCCESS.getMsg();
17         this.data = data;
18     }
19 
20     // 返回指定状态码,数据对象
21     public ResultVo(StatusCode statusCode, Object data) {
22         this.code = statusCode.getCode();
23         this.msg = statusCode.getMsg();
24         this.data = data;
25     }
26 
27     public ResultVo(ResultCode status, String msg, Object data) {
28         this.code = status.getCode();
29         this.msg = msg;
30         this.data = data;
31     }
32 
33     public static ResultVo success(Object data) {
34         return new ResultVo(ResultCode.SUCCESS, data);
35     }
36 
37     public static ResultVo fail(ResultCode status, String msg, Object data) {
38         return new ResultVo(status, msg, data);
39     }
40 }
View Code
复制代码

统一状态码

复制代码
 1 @Getter
 2 public enum ResultCode implements StatusCode {
 3     SUCCESS(1, "请求成功"),
 4     FAIL(-1, "操作失败"),
 5 
 6     ERROR_500(500, "服务器未知错误"),
 7     ERROR_400(400, "错误请求");
 8 
 9     private int code;
10     private String msg;
11 
12     ResultCode(int code, String msg) {
13         this.code = code;
14         this.msg = msg;
15     }
16 }
View Code
复制代码

测试代码

复制代码
1     @GetMapping("/response/demo")
2     public ResultVo paramValid() {
3         return new ResultVo(User.builder().id("1234").accountId("LS998").name("ls").build());
4     }
View Code
复制代码

至此,后端返回基本完成,效果如图

问题:如此配置,每次都需要后端开发new ResultVo(),重复工作太多。

解决:Springboot提供了@RestControllerAdvice注解进行控制器全局配置,默认控制全部controller,可通过参数basePackages进行某个包路径下controller控制。

复制代码
 1 @RestControllerAdvice(basePackages = "com.ls.code.valid")
 2 public class BaseResponseBodyAdvice implements ResponseBodyAdvice<Object> {
 3     
 4     @Override
 5     public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> converterType) {
 6         return true;
 7     }
 8 
 9     @Override
10     @SneakyThrows
11     public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
12         return ResultVo.success(data);
13     }
14 }
View Code
复制代码

测试代码:

复制代码
1     @GetMapping("/response/demo")
2     public User paramValid() {
3         return User.builder().id("1234").accountId("LS998").name("ls").build();
4     }
View Code
复制代码

效果如图,自动封装返回结构

问题1:如果某些开发,在此配置下,又在控制器里写了new Result(),会如何?

测试代码

复制代码
1     @GetMapping("/response/demo")
2     public ResultVo paramValid() {
3         return new ResultVo(User.builder().id("10086").name("ls").build());
4     }
View Code
复制代码

效果如图:显然需要对这种情况再次处理,因为没法保证所有开发都和你一样懒

问题2:如果返回String类型会如何

测试代码

复制代码
1     @GetMapping("/response/demo")
2     public String paramValid() {
3         return "测试代码";
4     }
View Code
复制代码

效果如图:内部报错为java.lang.ClassCastException: com.ls.code.valid.common.ResultVo cannot be cast to java.lang.String,显然需要对String类型的返回值进行处理。

问题3:业务中是否有需求为 只返回一个字符串,并不需要和前端交互。如:各种中间件的探活等。只需要返回字符串即可。

解决方案为 将探活接口controller放在全局控制器RestControllerAdvice外。但是不建议这样做,不优美。更好的做法为定义注解。对不需要控制的接口进行单独处理。

最终控制类代码如下:

复制代码
 1 @RestControllerAdvice(basePackages = "com.ls.code.valid")
 2 public class BaseResponseBodyAdvice implements ResponseBodyAdvice<Object> {
 3 
 4     @Autowired
 5     private ObjectMapper objectMapper;
 6 
 7 
 8     /**
 9      * @param methodParameter 方法返回的类型
10      * @param converterType   参数类型装换
11      * @return 返回true 则调用 beforeBodyWrite方法
12      */
13     @Override
14     public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> converterType) {
15         //返回类型为ResultVo 或者带有IgnoreResponseAdvice注解 则不进行包装
16         return !(methodParameter.getParameterType().isAssignableFrom(ResultVo.class)
17                 || methodParameter.hasMethodAnnotation(IgnoreResponseAdvice.class));
18     }
19 
20     @Override
21     @SneakyThrows
22     public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
23         //String类型返回值单独处理
24         if (data instanceof String) {
25             return objectMapper.writeValueAsString(new ResultVo(data));
26         }
27         return ResultVo.success(data);
28     }
29 }
View Code
复制代码

 全局异常处理

Springboot提供@ControllerAdvice注解,可以进行全局异常定制

我们所需处理的异常无非三种类型

1:各种参数校验异常,例:账号密码不能为空等 代码以spring-boot-starter-validation为例

如果不进行处理,效果如图

系统内部报错异常为BindException,对BindException异常进行处理。

代码如下:

复制代码
 1 @ControllerAdvice
 2 @ResponseBody
 3 public class ControllerExceptionAdvice {
 4     /**
 5      * 参数校验异常捕获
 6      *
 7      * @param e
 8      * @return
 9      */
10     @ExceptionHandler({BindException.class})
11     public ResultVo MethodArgumentNotValidExceptionHandler(BindException e) {
12         // 从异常对象中拿到ObjectError对象
13         ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
14         return new ResultVo(ResultCode.ERROR_400, objectError.getDefaultMessage());
15     }
16 }
View Code
复制代码

效果如图

2:自定义业务异常,开发中,经常要对用户的错误操作进行提示等,需要我们自定义异常,并给出相应的提示 例:商品已下架等

自定义异常代码

复制代码
 1 @Getter
 2 public class BusinessException extends RuntimeException {
 3     private Integer code;
 4     private String msg;
 5 
 6     public BusinessException(ResultCode resultCode, String message) {
 7         //设置错误详情
 8         super(message);
 9         this.code = resultCode.getCode();
10         this.msg = resultCode.getMsg();
11     }
12 
13     public BusinessException(String message) {
14         super(message);
15         this.code = ResultCode.FAIL.getCode();
16         this.msg = ResultCode.FAIL.getMsg();
17     }
18 }
View Code
复制代码

对自定义异常进行响应封装

复制代码
 1     /**
 2      * 业务异常捕获
 3      *
 4      * @param e
 5      * @return
 6      */
 7     @ExceptionHandler({BusinessException.class})
 8     public ResultVo MethodArgumentNotValidExceptionHandler(BusinessException e) {
 9         return new ResultVo(e.getCode(), e.getMsg(), e.getMessage());
10     }
View Code
复制代码

3:服务器异常,例:空指针  。由于异常种类众多,我们不能对每一种异常都做定制化,所以只对Exception进行封装,异常由开发自己保证,而且这些异常返回给前端并没有什么意义。

代码如下:

复制代码
1     @ExceptionHandler({Exception.class})
2     public ResultVo MethodArgumentNotValidExceptionHandler(Exception e) {
3         log.error(Throwables.getStackTrace(e));
4         return ResultVo.fail(ResultCode.ERROR_500, ResultCode.ERROR_500.getMsg(), e.getMessage());
5     }
View Code
复制代码

源代码已经上传:https://gitee.com/liushuai2716/code.git

 

posted @   怎么又被嫌弃了  阅读(53)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示