公司封装的 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 字符串的问题。


如果觉得还不错的话,关注、分享、在看, 原创不易,且看且珍惜~

 

posted on 2021-11-22 14:57  XuHe1  阅读(147)  评论(0编辑  收藏  举报