前言
在“如何封装一个生产级 Result 实体” ,我们规范了响应格式。但随之而来的是 Controller 中大量重复的编码:
@GetMapping("/users")
public Result<List<User>> getUsers() {
return Result.ok(userService.getUsers());
}
@GetMapping("/user/{id}")
public Result<User> getUserById(@PathVariable Long id) {
return Result.ok(userService.getUserByid(id));
}
这种写法在每个方法中都需要手动声明返回值类型并构建 Result 对象,产生了大量冗余代码。为了让 Controller 只关注业务数据,我们可以利用 ResponseBodyAdvice 自动完成结果的包装,干掉这些“不好看”的模板代码。
ResponseBodyAdvice
ResponseBodyAdvice 本质上是 Spring MVC 提供的拦截增强机制。它能在 ResponseBody 写入响应流之前,对返回结果进行“二次加工”。
我将演示如何通过实现该接口,实现接口返回值的自动包装.
IgnoreResponseWrapper
在使用 ResponseBodyAdvice 之前,我们定义一个 IgnoreResponseWrapper 注解,这个注解的作用是告诉 ResponseBodyAdvice 只要被 IgnoreResponseWrapper 注解的方法或者类,都不需要”加工返回结果了“
@Documented
@Target({ElementType.METHOD, ElementType.TYPE}) // 可直接用于接口类或者方法上
@Retention(RetentionPolicy.RUNTIME) // 声明周期:运行时有效,可用于反射
public @interface IgnoreResponseWrapper {
}
ResponseBodyAdvice 接口
ResponseBodyAdvice 接口中有两个方法,分别是:
-
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);- 用于指示当前方法是否需要“加工”,及调用下面的
beforeBodyWrite方法处理返回值。
- 用于指示当前方法是否需要“加工”,及调用下面的
-
@Nullable T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);- 处理返回值的核心逻辑
ResponseBodyAdvice 实现
@AllArgsConstructor
@RestControllerAdvice
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
private final ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
// 如果方法或类上标记了 @IgnoreResponseWrapper,则不进行包装
if (returnType.getDeclaringClass().isAnnotationPresent(IgnoreResponseWrapper.class) ||
returnType.hasMethodAnnotation(IgnoreResponseWrapper.class)) {
return false;
}
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
// 如果已经是 Result 类型,直接返回
if (body instanceof Result) {
return body;
}
// 如果是 String 类型,需要手动序列化,否则会报 ClassCastException
if (body instanceof String) {
try {
response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_VALUE);
return objectMapper.writeValueAsString(Result.success(body));
} catch (Exception e) {
throw new HttpMessageNotWritableException("接口响应处理序列化 String 响应失败", e);
}
}
// 统一包装为 Result
return Result.success(body);
}
}
现在我们就可以简化 Controller 中返回数据的格式了!
@GetMapping("/users")
public List<User> getUsers() {
return userService.getUsers();
}
@GetMapping("/user/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
当然,如果某些 Controller 中就需要显示使用 Result,就可以使用 IgnoreResponseWrapper 注解了
@PutMapping("/user/{id}")
public Result<Long> updaeUserById(@PathVariable Long id) {
return userService.updaeUserById(id);
}