大家好,我是码哥,《Redis 高手心法》作者。
假如你作为项目组长,为 Spring Boot 项目设计一个规范的统一的RESTfulAPI 响应框架。
前端或者移动端开发人员通过调用后端提供的RESTful接口完成数据的交换。
常见的统一响应数据结构如下所示:
统一接口响应能够减少团队内部不必要的沟通;减轻接口消费者校验数据的负担;降低其他同事接手代码的难度;提高接口的健壮性和可扩展性。
除此之外,还需要实现一个统一的异常处理框架。通过这个全局异常处理,可以避免将异常信息和系统敏感信息直接抛出给客户端。
针对特定异常捕获后可以重新对异常输出信息做编排,提高交互友好度,同时可以记录异常信息。
实现思路
我们需要定义一个 Result类,在类中定义需要返回的字段信息,比如状态码、结果描述、结果数据集等。
接口的状态码很多,我们可以用一个枚举类进行封装。于是就有了下面的代码。顺便说一句,推荐大家使用 lombok,减少繁琐的 set、get、构造方法。
状态码枚举
统一响应封装
封装一个固定返回格式的结构对象:Result。
有了统一响应体,于是你就可以在 Controller 返回结果时这样写:
唐二婷:Controller 类中每一个方法的返回值类型都只能是这个响应对象类,太不优雅了。
这个问题问得好。
为了能够实现统一的响应对象,又能优雅的定义 Controller 类的方法,使其每个方法的返回值是其应有的类型。
主要是借助RestControllerAdvice注解和ResponseBodyAdvice接口来实现对接口响应给客户端之前封装成 Result。
全局统一 Restful API 统一返回
Spring Boot 框架其实已经帮助开发者封装了很多实用的工具,比如 ResponseBodyAdvice 接口,我们可以利用来实现数据格式的统一返回。
忽略响应包装
有些场景下我们不希望 Controller 方法的返回值被包装为统一响应对象,可以先定义一个忽略响应封装的注解,配合后续代码实现。
ResponseBodyAdvice 接口
这是 Spring 框架提供的一个接口,我们可以利用它实现对接口数据格式统一封装。
ResponseBodyAdvice可以对 controller 层中的拥有@ResponseBody 注解属性的方法进行响应拦截,用户可以利用这一特性来封装数据的返回格式,也可以进行加密、签名等操作。
实现该接口的类还需要添加 @RestControllerAdvice注解,这是一个组合注解,由@ControllerAdvice、@ResponseBody组成,而@ControllerAdvice继承了@Component,因此@RestControllerAdvice本质上是个Component。
本质上就是使用 Spring AOP 定义的一个切面,作用于 Controller 方法执行完成后的增强操作。
ResponseBodyAdvice接口有两个方法需要重写。
- supports方法:实际开发中不一定所有的方法封装统一接口响应,这里可以根据MethodParameter进行过滤,此方法返回 true 则会走过滤,即会调用beforeBodyWrite方法,否则不会调用。
- beforeBodyWrite:编写具体响应客户端之前的的数据逻辑。
@RestControllerAdvice 未生效?
ResponseBodyAdvice 接口实现类 GlobalResponseAdvice 没有被 Spring 管理。
因为启动类上的 @SpringbootApplication 默认扫描本包和子包。
比如 GlobalResponseAdvice 在 zero.magebyte.shop.common 包下,而启动类在 zero.magebyte.shop.order.server 包,那么 GlobalResponseAdvice 就不会生效。
为了防止全局接口统一响应处理器 GlobalResponseAdvice类未被扫描到,建议在启动类上加上包扫描。
测试
定义一个 Controller 类来进行简单的开发和测试。
Result 返回类型
method1 方法返回类型是 Result,所以不会再次封装,而是直接返回 Result 结构,并以 Content-Type: application/json格式响应给客户端。
void 类型
method2 方法返回类型是 void,会封装成 Result 结构,并以 Content-Type: application/json格式响应给客户端。只不过 data 数据是 null。
@IgnoreRestFulAPI 注解
method3 被 @IgnoreRestFulAPI 注解,不会被封装 Result 结构,直接返回。
String 类型
默认 String 类型的数据响应给客户端的格式为 text/html,为了统一响应格式,需要手动设置响应类型为 json,如下所示。
响应给客户端的格式就是一个 Result JSON 对象,Content-Type: application/json。
否则将会以 Content-Type: text/html;charset=UTF-8响应呵客户端。
另外需要注意的是,如果你使用了 swagger,以上代码会导致 swagger 无法访问。
报错如下:
原因:因为统一响应拦截器对 swagger 的接口做了拦截并对结果做了包装,导致返回结构发生后变化,swagger 无法解析。
解决方案:修改统一响应处理器拦截的范围,配置散列包路径。你可以指定 @RestControllerAdvice(basePackages = {"xxx.xxx"})项目的 controller 目录即可。
统一异常处理
唐二婷:虽然有了统一结构响应,接口可以直接返回实际数据,但是每个接口都要根据业务的要求进行不同程度的 try..catch 处理异常,如果有几百个接口,不仅编程工作量大,可读性也差。
兵来将挡,水来土掩。这样写代码并不是不好看,而是十分垃圾!!!
如下是我们自定义的业务异常。
在 Spring Boot 中,我们不用这样写,可以继续利用 @RestControllerAdvice
注解和@ExceptionHandler
注解实现全局异常处理器,拦截 Controller 层抛出的异常。
实现方式
新增 GlobalExceptionHandler 类,编写统一异常处理,类上面添加 @RestControllerAdvice 注解就开启了全局异常处理。
我们可以在类面创建多个方法,并在方法上添加 @ExceptionHandler 注解,对不同的异常进行定制化处理,并统一返回 Result 结构响应给客户端。
测试代码
故意制造一个除 0 异常。
自定义抛出业务异常。
总结
RestControllerAdvice注解和ResponseBodyAdvice接口来实现对接口响应给客户端之前封装成 Result。
统一接口响应客户端,减少团队内部不必要的沟通;减轻接口消费者校验数据的负担;降低其他同事接手代码的难度;提高接口的健壮性和可扩展性。
通过 @RestControllerAdvice 注解和@ExceptionHandler` 注解实现统一异常处理,能够减少代码的重复度和复杂度,有利于代码的维护,并且能够快速定位到 BUG,大大提高我们的开发效率。