要解决的问题
通常情况下我们设计接口返回时,我们会对接口返回一个统一的格式。
@RestContrller
public class UserController{
@GetMapping("user")
public Result<User> getUser(){
User user = User.builder().id(1).name("ghimi").age(24).build();
return ResultWrapper.success(user);
}
}
// 定义返回数据结构
public interface IResult {
Integer getCode();
String getMessage();
}
// 常用接口的枚举
public enum ResultEnum implememnts IResult{
SUCCESS(2000,"接口调用成功"),
VALIDATE_FAILED(4003,"参数校验失败"),
COMMON_FAILED(5003,"接口调用失败"),
FORBIDDEN(4001,"没有权限访问资源");
private Integer code;
private String message;
}
// 统一返回数据结构
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
private Integer code;
private String message;
private T data;
public static <T> Result<T> success(T data) {
return instance(ResultEnum.SUCCESS,data);
}
public static <T> Result<T> instance(ResultEnum result,String message, T data) {
return instance(result.getCode,message,data);
}
public static <T> Result<T> instance(ResultEnum result, T data) {
return instance(result.getCode,result.getMessage(),data);
}
public static <T> Result<T> instance(Integer code, String message, T data) {
Result<T> result = new Result<>();
result.setCode(code);
result.setMessage(message);
result.setData(data);
return result;
}
}
由于返回结果都是统一的 Result 格式,因此我们可以针对这部分统一返回格式进行封装。
统一包装处理
Spring 中提供了一个类 ResponseBodyAdvice ,能帮助我们实现上述需求:
public interface ResponseBodyAdvice<T> {
// 判断是否支持切片
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
// 在响应body写入前执行写入
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response);
}
ResponseBodyAdvice 是对 Controller 返回的内容在 HttpMessageConverter 进行类型转换之前拦截,进行相应的处理操作后,再将结果返回给客户端。
那这样就可以把统一包装的工作放到这个类里面:
- supports:判断是否要交给 beforeBodyWrite 方法执行,ture:需要;false:不需要
- beforeBodyWrite:对 response 进行具体的处理
统一封装实现
基于 Spring 提供的 ResponseBodyAdvice 实现,我们可以对响应进行封装:
@Slf4j
// 声明这是一个响应拦截器
@RestControllerAdvice(basePackages = "cn.lg14.pg3.webgame.dispatch.web")
// 实现 ResponseBodyAdvice 重写 supports 方法,这里泛型对象为 Object 表示对全部返回结果都进行封装
public class ResponseControllerAdvice implements ResponseBodyAdvice<Object> {
@Autowired
ObjectMapper mapper;
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
Class<?> type = returnType.getParameterType();
// 如果设置了raw result 注解,声明封装逻辑执行的方式
// 1. 如果返回结果已经是封装好的 Result 格式了,则不处理
// 2. 如果方法被 @UnwrappedBody 注解,则不做处理
if (Result.class.isAssignableFrom(type)
|| Objects.nonNull(returnType.getExecutable().getAnnotation(UnwrappedBody.class))) {
log.info("the method {} has annotated by raw result",
returnType.getExecutable().getName());
return false;
}
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
if (StringHttpMessageConverter.class.isAssignableFrom(selectedConverterType)) {
try {
// 对 被 StringHttpMessageConverter 处理的返回结果做特殊处理
// 重新封装字为字符串,因为之后会被强制转换为 String 类型
// 因此这里提前通过序列化的方式处理为json字符串格式
return mapper.writeValueAsString(ResultWrapper.success(body));
} catch (JsonProcessingException e) {
// 如果报错则抛出异常,应该
log.warn("wrap body has occurred an exception {}", body, e);
return body;
}
}
// 其余返回对象通过 ResultWrapper.success 对象封装
return ResultWrapper.success(body);
}
}
对于响应结果为 JSON对象时,Spring 会统一交给 MappingJackson2HttpMessageConverter
进行序列化处理.
经过这样改造,既能实现对 Controller 返回的数据进行统一包装,又不需要对原有代码进行大量的改动。
对于返回结果为 String 类型的特殊处理
由于 Spring 内部的设计机制,当我们处理 String 类型的数据的时候不能直接进行封装,因为这样会导致 StringHttpMessageConverter 与 MappingJackson2HttpMessageConverter 发生冲突,从而导致统一封装失败,因为我们需要单独做个判断,对 String 类型的数据进行单独处理.
if (StringHttpMessageConverter.class.isAssignableFrom(selectedConverterType)) {
try {
// 对 被 StringHttpMessageConverter 处理的返回结果做特殊处理
// 重新封装字为字符串,因为之后会被强制转换为 String 类型
// 因此这里提前通过序列化的方式处理为json字符串格式
return mapper.writeValueAsString(ResultWrapper.success(body));
} catch (JsonProcessingException e) {
// 如果报错则抛出异常,应该
log.warn("wrap body has occurred an exception {}", body, e);
return body;
}
}