微服务中,由于各业务团队之间的对接,各个团队之间需要统一返回格式,这样解析时不容易出现错误。因此,有必要统一返回格式。下面我说下项目中常见的两种统一和变更返回值格式的方式
ResponseBodyAdvice切面方式
这种方式简单易实现,仅仅只需要实现ResponseBodyAdvice方法,然后指定要拦截的包路径即可
代码语言:txt复制@ControllerAdvice("com.example.ut")
public class RestControllerAdvice implements ResponseBodyAdvice<Object> {
private static final String VOID = "void";
//判断是否要执行beforeBodyWrite方法,true为执行,false不执行
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
if (VOID.equals(getReturnName(returnType))) {
return null;
}
//基础类型做特殊处理,如实际操作过程没有此场景可无须使用
if (isBasicType(returnType)) {
return body;
}
if (body == null) {
return ApiResponse.of(null,StatusCode.OK);
}
if (!(body instanceof ApiResponse)) {
return ApiResponse.of(body, StatusCode.OK);
}
else {
ApiResponse commonResult = (ApiResponse) body;
return commonResult;
}
}
private String getReturnName(MethodParameter returnType) {
if (returnType == null || returnType.getMethod() == null) {
return StringUtils.EMPTY;
}
return returnType.getMethod().getReturnType().getName();
}
private boolean isBasicType(MethodParameter returnType) {
if (returnType == null || returnType.getMethod() == null) {
return true;
}
String name = returnType.getMethod().getReturnType().getSimpleName();
switch (name) {
case "String":
case "byte[]":
case "ResponseEntity":
return true;
default:
return false;
}
}
}
测试时使用通用的返回通用类作为测试依据
当我们再返回值没有使用ApiResponse作为包装对象时,此切面仍然为我们实现了包装
代码语言:txt复制@RestController
public class ResponseController {
@PostMapping("test")
public Circle testReturn(){
Circle circle = new Circle();
circle.setRadius(5.0d);
return circle;
}
}
返回值为
代码语言:txt复制{
"code": 0,
"message": "OK",
"body": {
"radius": 5.0,
"area": 78.53981633974483
}
}
究其原因则是因为在源码中,初始化时就对切面进行了处理,从而可以执行相应的操作,具体可以参考RequestMappingHandlerAdapter#initControllerAdviceCache
使用更为底层的HandlerMethodReturnValueHandler来自定义返回值类型
在操作的过程中也是同样的逻辑
代码语言:txt复制public class ApiResponseHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
private MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//是否支持handleReturnValue
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class))
&& !ApiResponse.class.equals(returnType.getParameterType());
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest) throws Exception {
// TODO 可通过客户端的传递的请求头来切换不同的响应体的内容
mavContainer.setRequestHandled(true);
// returnValue = POJO
ApiResponse apiResponse = ApiResponse.ok(returnValue);
HttpServletResponse response = (HttpServletResponse) webRequest.getNativeResponse();
response.addHeader("v", "3");
ServletServerHttpResponse httpOutMessage = createOutputMessage(webRequest);
converter.write(apiResponse, MediaType.APPLICATION_JSON, httpOutMessage);
}
protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
Assert.state(response != null, "No HttpServletResponse");
return new ServletServerHttpResponse(response);
}
}
但是由于spring的默认处理类是RequestResponseBodyMethodProcessor,它是根据判断是否有@ResponseBody注解来处理的
且他是在自定义HandlerMethodReturnValueHandler之前执行的,所以我们需要把我们的自定义且他是在自定义HandlerMethodReturnValueHandler放到最前面执行才可以
代码语言:txt复制@Configuration
public class WebMvcConfiguration {
@Autowired
public void resetRequestMappingHandlerAdapter(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
List<HandlerMethodReturnValueHandler> oldReturnValueHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> newReturnValueHandlers = new ArrayList<>(oldReturnValueHandlers);
newReturnValueHandlers.add(0, new ApiResponseHandlerMethodReturnValueHandler());
requestMappingHandlerAdapter.setReturnValueHandlers(newReturnValueHandlers);
}
}
源码执行顺序如下
代码语言:txt复制 private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList(20);
handlers.add(new ModelAndViewMethodReturnValueHandler());
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new ResponseBodyEmitterReturnValueHandler(this.getMessageConverters(), this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
handlers.add(new StreamingResponseBodyReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));
handlers.add(new HttpHeadersReturnValueHandler());
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
handlers.add(new ServletModelAttributeMethodProcessor(false));
//先执行了RequestResponseBodyMethodProcessor
handlers.add(new RequestResponseBodyMethodProcessor(this.getMessageConverters(), this.contentNegotiationManager, this.requestResponseBodyAdvice));
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
//后执行自定义HandlerMethodReturnValueHandler
if (this.getCustomReturnValueHandlers() != null) {
handlers.addAll(this.getCustomReturnValueHandlers());
}
if (!CollectionUtils.isEmpty(this.getModelAndViewResolvers())) {
handlers.add(new ModelAndViewResolverMethodReturnValueHandler(this.getModelAndViewResolvers()));
} else {
handlers.add(new ServletModelAttributeMethodProcessor(true));
}
return handlers;
}
这样即可达到与上述ResponseBodyAdvice切面方式一样的效果
参考文章Spring Boot 中如何统一 API 接口响应格式?