SpringBoot中统一API返回格式的两种方式
Posted 微瞰技术
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot中统一API返回格式的两种方式相关的知识,希望对你有一定的参考价值。
微服务中,由于各业务团队之间的对接,各个团队之间需要统一返回格式,这样解析时不容易出现错误。因此,有必要统一返回格式。下面我说下项目中常见的两种统一和变更返回值格式的方式
ResponseBodyAdvice切面方式
这种方式简单易实现,仅仅只需要实现ResponseBodyAdvice方法,然后指定要拦截的包路径即可
@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作为包装对象时,此切面仍然为我们实现了包装
@RestController
public class ResponseController
@PostMapping("test")
public Circle testReturn()
Circle circle = new Circle();
circle.setRadius(5.0d);
return circle;
返回值为
"code": 0,
"message": "OK",
"body":
"radius": 5.0,
"area": 78.53981633974483
究其原因则是因为在源码中,初始化时就对切面进行了处理,从而可以执行相应的操作,具体可以参考RequestMappingHandlerAdapter#initControllerAdviceCache
使用更为底层的HandlerMethodReturnValueHandler来自定义返回值类型
在操作的过程中也是同样的逻辑
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放到最前面执行才可以
@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);
源码执行顺序如下
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切面方式一样的效果
以上是关于SpringBoot中统一API返回格式的两种方式的主要内容,如果未能解决你的问题,请参考以下文章