• 作用:

允许在执行标有@ResponseBody注解或响应内容是ResponseEntity的控制器方法之后,但在使用HttpMessageConverter类编写主体之前自定义响应。

  • 实践:

使用ResponseBodyAdvice统一处理包装Controller方法中返回值,不用在每个方法都重复写Result<类型>

  • 说明:
  • 是否执行增强的方法beforeBodyWrite()
@Override
public boolean supports(MethodParameter returnType,
                        Class<? extends HttpMessageConverter<?>> converterType) {
    // ......
}
  • 对返回结果集增强的逻辑
@Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        // ......
    }

问题一

在beforeBodyWrite方法中如果直接返回Resp对象,对字符串类型不做任何处理。会导致方法(writeWithMessageConverters)使用字符串转化器(StringHttpMessageConverter)处理Resp对象,出现异常问题。

  • AbstractMessageConverterMethodProcessor源码分析
// 对返回的String类型进行了特殊处理
		if (value instanceof CharSequence) {
			body = value.toString();
			valueType = String.class;
			targetType = String.class;
		}
		else {
			body = value;
			valueType = getReturnValueType(body, returnType);
			targetType = GenericTypeResolver.resolveType(getGenericType(returnType), returnType.getContainingClass());
		}

// ...........


if (selectedMediaType != null) {
			selectedMediaType = selectedMediaType.removeQualityValue();
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?
						(GenericHttpMessageConverter<?>) converter : null);
               
                // 1.遍历this.messageConverters,识别到converter转换器是StringHttpMessageConverter
				if (genericConverter != null ?
						((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
                 
                // 2.调用自定义的beforeBodyWrite方法,得到body类型是Resp类型数据
					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<? extends HttpMessageConverter<?>>) converter.getClass(),
							inputMessage, outputMessage);
					if (body != null) {
						Object theBody = body;
						LogFormatUtils.traceDebug(logger, traceOn ->
								"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");
						addContentDispositionHeader(inputMessage, outputMessage);
						if (genericConverter != null) {
							genericConverter.write(body, targetType, selectedMediaType, outputMessage);
						}
						else {
                
       // 3.采用StringHttpMessageConverter处理Resp类型的数据,导致类型转换异常
							((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
						}
					}
					else {
						if (logger.isDebugEnabled()) {
							logger.debug("Nothing to write: null body");
						}
					}
					return;
				}
			}
		}
  • 源码分析
  1. 遍历this.messageConverters,识别到converter转换器是StringHttpMessageConverter
  2. 调用自定义的beforeBodyWrite方法,得到body类型是Resp类型数据
  3. 采用StringHttpMessageConverter处理Resp类型的数据,导致类型转换异常
  1. MappingJackson2HttpMessageConverter、StringHttpMessageConverter

long、int等类型使用MappingJackson2HttpMessageConverter转化器处理

String类型使用 StringHttpMessageConverter转化器处理

  1. 解决方案

在beforeBodyWrite方法中进行处理,如果遇到字符串,则提前进行处理返回。

问题二

  • 不处理Resp统一返回对象,使得报文data数据显示不对
{
  "code": 200,
  "msg": "SUCCESS",
  "success": true,
  "data": {
    "code": 400,
    "msg": "请求失败!",
    "success": false,
    "data": null
  }
}
  • 原因:先进行了异常处理,然后再走了beforeBodyWrite方法
  • 解决方法:如果body为Resp对象,则直接返回
if (body instanceof Resp) {
    return body;
}

问题三

  • 结合swagger使用时,导致swagger页面访问不了
  • 原因:

swagger相关api返回的数据默认也走了beforeBodyWrite方法,并对其数据进行了处理,导致swagger获取不到它想要的格式

  • 解决方法

在supports中排除swagger相关类

@Override
    public boolean supports(MethodParameter returnType,
                            Class<? extends HttpMessageConverter<?>> converterType) {
        // 排除拦截swagger相关
        return !returnType.getDeclaringClass().getName().contains(SPRING_FOX_STR);
    }

代码如下

/**
 * @Description: 拦截Controller方法的返回值,统一处理返回值
 * @Author party-abu
 * @Date 2021/12/27 16:47
 */
@RestControllerAdvice
public class GlobalResultHandler implements ResponseBodyAdvice<Object> {

    private static final Logger log = LoggerFactory.getLogger(GlobalResultHandler.class);

    private static final String SPRING_FOX_STR = "springfox";

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 是否执行 beforeBodyWrite方法
     * true:执行 false:不执行
     *
     * @param returnType
     * @param converterType
     * @return
     */
    @Override
    public boolean supports(MethodParameter returnType,
                            Class<? extends HttpMessageConverter<?>> converterType) {
        // 排除拦截swagger相关
        return !returnType.getDeclaringClass().getName().contains(SPRING_FOX_STR);
    }

    /**
     * 对返回值进行处理
     *
     * @param body
     * @param returnType
     * @param selectedContentType
     * @param selectedConverterType
     * @param request
     * @param response
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {

        // 遇到字符串时,提前返回处理,
        // 避免使用StringHttpMessageConvert处理使用Resp类型封装字符串后的数据,导致数据处理异常问题
        if (body instanceof String) {
            try {
                return objectMapper.writeValueAsString(Resp.data(body));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }

        // 解决与统一异常处理产生的冲突问题
        if (body instanceof Resp) {
            return body;
        }

        return Resp.data(body);
    }

    /**
     * 业务异常统一管理
     *
     * @param biz
     * @return
     */
    @ExceptionHandler(BizException.class)
    public Resp<Object> bizExceptionHandler(BizException biz) {
        log.error("业务异常 状态码:{},提示信息:{}", biz.getCode(), biz.getMsg());
        return Resp.fail(biz.getCode(), biz.getMsg());
    }

    /**
     * 全局异常统一管理
     *
     * @param r
     * @return
     */
    @ExceptionHandler(RuntimeException.class)
    public Resp<Object> bizExceptionHandler(RuntimeException r) {
        r.printStackTrace();
        return Resp.fail(ResultCodeEnum.INTERNAL_SERVER_ERROR);
    }

}