微服務中,由於各業務團隊之間的對接,各個團隊之間需要統一返回格式,這樣解析時不容易出現錯誤。因此,有必要統一返回格式。下面我說下項目中常見的兩種統一和變更返回值格式的方式
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切面方式一樣的效果