公司封装的 ResponseBodyAdvice 有问题,很严重
ResponseBodyAdvice 是个什么东西?我们先从名字能看出,首先是个 Advice,其次,ResponseBody 说明了这个 Advice 作用的切点是在响应体。
看下源码:
/**
* Allows customizing the response after the execution of an {@code @ResponseBody}
* or a {@code ResponseEntity} controller method but before the body is written
* with an {@code HttpMessageConverter}.
*
* <p>Implementations may be registered directly with
* {@code RequestMappingHandlerAdapter} and {@code ExceptionHandlerExceptionResolver}
* or more likely annotated with {@code @ControllerAdvice} in which case they
* will be auto-detected by both.
*
* @author Rossen Stoyanchev
* @since 4.1
* @param <T> the body type
*/
public interface ResponseBodyAdvice<T> {
/**
* Whether this component supports the given controller method return type
* and the selected {@code HttpMessageConverter} type.
* @param returnType the return type
* @param converterType the selected converter type
* @return {@code true} if {@link #beforeBodyWrite} should be invoked;
* {@code false} otherwise
*/
boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);
/**
* Invoked after an {@code HttpMessageConverter} is selected and just before
* its write method is invoked.
* @param body the body to be written
* @param returnType the return type of the controller method
* @param selectedContentType the content type selected through content negotiation
* @param selectedConverterType the converter type selected to write to the response
* @param request the current request
* @param response the current response
* @return the body that was passed in or a modified (possibly new) instance
*/
@Nullable
T beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response);
}
简单翻一下:
这个接口使得你可以定制返回结果,执行的阶段是:controller 方法执行后,调用 MessageConverter 写回结果前执行。
公司前同事基于这个接口的封装:
@RestControllerAdvice(basePackages={"com.xx.xx"})
@Slf4j
public class ResultResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
NoResponseAdvice noResponseAdvice = methodParameter.getMethodAnnotation(NoResponseAdvice.class);
if(noResponseAdvice != null) {
return false;
}
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if (body instanceof ResponseBean) {
return body;
}
if (body instanceof String) {
return JSONObject.toJSONString ( new ResponseBean(true,body,ResponseCode.SUCCESS) );
}
return new ResponseBean(true,body,ResponseCode.SUCCESS);
}
}
不知道大家有没有发现问题,如果没发现的话,请看下面接口:
@ResponseBody
@GetMapping("/hello")
public String hello() {
return null;
}
这个接口会报一个错误:ResponseBean 无法强制转化为 String 。
出现这个问题的原因分析下:
接口签名返回参数是 String,并且返回的是 null , 在全局处理时,没有处理 null 的情况,因此会返回一个 ResponseBean 对象。之后会走 MessageConverter 的 write 方法。Spring 会根据你的方法返回 valueType 和 MediaType 智能地给你选择了 StringHttpMessageConverter ,这时就出现了类型转化错误。
熟悉 SpringMVC 源码的知道:
HandlerAdaptor.handlerInternal-> invocableHanderMethod.invokeAndHandle-> returnValueHandlers.handleReturnValue-> RequestResponseBodyMethodProcessor.handleReturnValue-> AbstractMessageConverterMethodProcessor.writeWithMessageConverters
基于以上问题,我重新修改了封装的代码:
@RestControllerAdvice(basePackages={"com.xx.xx"})
@Slf4j
public class ResultResponseAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
NoResponseAdvice noResponseAdvice = methodParameter.getMethodAnnotation(NoResponseAdvice.class);
if(noResponseAdvice != null) {
return false;
}
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if (body instanceof ResponseBean) {
return body;
}
// 字符串以及 null 字符串处理
if (body instanceof String || (body == null && methodParameter.getParameterType() == String.class)) {
return JSONObject.toJSONString ( new ResponseBean(true,body,ResponseCode.SUCCESS) );
}
// 对象(非字符串)及 null 对象
return new ResponseBean(true,body,ResponseCode.SUCCESS);
}
}
这里前端可能会收到 json 字符串,可以通过添加 Accept 头要求服务端返回 json 即可。
目前前端流行的 axios 的 responseType 默认就是 json,完全不用担心 json 字符串的问题。
如果觉得还不错的话,关注、分享、在看, 原创不易,且看且珍惜~