统一响应结果和全局异常处理
- 前言
- 统一响应结果
- 定义响应结构
- 定义响应对象
- 定义响应状态对象
- 统一返回响应对象
- 定义 Controller 拦截类
- 全局异常处理
前言
做个功能之前我们想一下为什么要做统一响应结果和全局异常处理呢?
这是因为我们的项目采用的是前后端分离开发,并且使用 Restful 风格的接口形式,返回的业务数据以及异常都是以 JSON 的格式返回,但是目前我们返回的数据格式都是不一致的,比如如下的代码:
@GetMapping("/{id}")
@Operation(summary = "根据用户ID查询用户")
public UserEntity get(@PathVariable Long id) {return userService.get(id);
}@GetMapping("/list")
@Operation(summary = "查询全部")
public List<UserEntity> lists() {return userService.lists();
}@GetMapping("/page")
@Operation(summary = "分页查询")
public Page<UserEntity> page(int page, int size) {return userService.page(PageRequest.of(page - 1, size));
}@PostMapping
@Operation(summary = "新增用户")
public void save(@RequestBody UserEntity user) {userService.save(user);
}
由上面的接口返回值可以看出,我们4个接口就有4中返回数据格式,这时前端工作人员在对接的时候就接口她们就会很难受,需要写很多的处理来处理不同的返回数据,非常的不方便,而且我们自己使用起来也不好管理,所以在实际开发中,为了降低开发人员之间的沟通成本,一般返回结果会定义成一个统一格式,具体的格式根据实际开发业务不同有所区别。
统一响应结果
定义响应结构
从前端的角度思考,调用后端接口后如果成功了则拿到想要的数据或者对应的信息提示,如果不成功也应该有相应的信息提示。从这个角度出发,我们将响应结果设计为:
code
:响应状态码,根据状态码判断是否成功
message
:提示信息,根据调用的接口自定义
body
:返回的数据
响应的结构如下:
{"code": 200,"message": "成功","body": [{"createUser": "admin","updateUser": "admin","createTime": "2023-12-09 14:14:20","updateTime": "2023-12-09 14:14:20","id": 2,"name": "金克斯","nickname": "爆爆","age": 18,"email": null,"password": "666"}]
}
定义响应对象
Spring 官方源码里面也有一个类似的对象,类名为 ResponseEntity
,如果你想直接用这个类也行的,但是这个类我使用起来有点不合我的习惯,所以还是自定义一个好点。
@Data
public class Response<T> implements Serializable {@Schema(description = "响应状态")private Integer code;@Schema(description = "响应信息")private String message;@Schema(description = "响应数据")private T body;/*** 私有化构造函数*/private Response() {}/*** 构建 Response<T> 对象** @param body 响应数据* @param code 响应状态对象*/public static <T> Response<T> build(@Nullable T body, ResponseCode code) {return build(body, code.getCode(), code.getMessage());}/*** 构建 Response<T> 对象** @param body 响应数据* @param code 响应状态* @param message 响应信息*/public static <T> Response<T> build(@Nullable T body, Integer code, String message) {Response<T> response = new Response<>();response.setCode(code);response.setMessage(message);response.setBody(body);return response;}/*** 请求成功返回不带数据*/public static <T> Response<T> ok() {return build(null, ResponseCode.OK);}/*** 请求成功返回带数据*/public static <T> Response<T> ok(T body) {return build(body, ResponseCode.OK);}/*** 请求失败返回500*/public static <T> Response<T> error() {return build(null, ResponseCode.ERROR);}/*** 请求失败状态码*/public static <T> Response<T> error(ResponseCode code) {return build(null, code);}/*** 自定义请求失败信息*/public static <T> Response<T> error(Integer code, String message) {return build(null, code, message);}}
定义响应状态对象
public enum ResponseCode {/*** 成功*/OK(200, "OK"),/*** 失败*/ERROR(500,"系统异常,请稍后重试");private final int code;private final String message;ResponseCode(int code, String message) {this.code = code;this.message = message;}public int getCode() {return code;}public String getMessage() {return message;}
}
统一返回响应对象
这里有两种方式可以做到全局返回我们定义好的 Response
对象,第一种就是在定义方法的时候返回值都定义为 Resopnse
对象,这是可以完成的,但是我们还有更高级一点的用法,使用 Spring ResponseBodyAdvice
类即可,ResponseBodyAdvice的作用就是拦截Controller方法的返回值,统一处理返回值/响应体,一般用来统一返回格式,加解密,签名等等。
我们这里采用第二种方式来完成统一响应结果。
定义 Controller 拦截类
@RestControllerAdvice(basePackages = {"com.xm.module"})
public class ResponseAdviceHandler implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {// 响应都需要被拦截return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body instanceof String) {// String 类型不能封装成 Response 返回return JSONUtil.toJsonStr(Response.ok(body));} else if (body instanceof Response) {// 如果是 Response 就直接返回了return body;} else {// 统一响应对象return Response.ok(body);}}
}
@RestControllerAdvice
是@RestController
注解的增强,可以实现三个方面的功能:
- 全局异常处理
- 全局数据绑定
- 全局数据预处理
basePackages
表示是只拦截 com.xm.module 下的响应
JSONUtil.toJsonStr(Response.ok(body));
这个 JSONUtil 我是用的 hutool 工具包里面的,好用滴很。有需要的自己导入。Hutool 官网
启动程序测试一下
这里报了一个异常,而且我们发现异常信息的响应结果和我们预想的不一致,这个我们在下面全局异常拦截的时候在进行处理,先把这个接口异常给解决一下。
异常的大致意思是不能初始化代理,没有 session,这是因为我们使用了懒加载的方法,修改一下这个接口方法。
@Override
public UserEntity get(Long id) {return userRepository.findById(id).orElse(null);
}
ok,接口请求成功,并且响应的结构和我们预想的一致,nice。
全局异常处理
刚才我们看到,在接口抛出异常的时候,我们的响应结构和我们预想的不一致,我们希望在抛出异常的时候也能返回我们的 Response
对象,怎么实现呢,还是老办法,上面我们提到了 @RestControllerAdvice
是 @RestController
注解的增强,可以实现全局异常处理,那我们就在使用这个注解进行全局异常的拦截处理吧。
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 全局异常处理。* ExceptionHandler(Exception.class) 表示拦截 Exception 异常,这是所有异常都拦截* 我们还可以配置 ExceptionHandler(XXException.class) 等等进行更加精细的拦截操作** @param e 异常* @return Response*/@ExceptionHandler(Exception.class)public Response<String> exception(Exception e) {return Response.error(ResponseCode.ERROR);}}
编写好了这个之后,我们再来测试一下异常的情况下返回值是什么样的,
ok,异常拦截成功,关于更多的异常,可以自己定义拦截即可,本节内容就到这里了,bye~