ResponseBodyAdvice类使用以及问题处理
-
作用:
允许在执行标有@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;
}
}
}
- 源码分析
遍历this.messageConverters,识别到converter转换器是StringHttpMessageConverter
调用自定义的beforeBodyWrite方法,得到body类型是Resp类型数据
采用StringHttpMessageConverter处理Resp类型的数据,导致类型转换异常
-
MappingJackson2HttpMessageConverter、StringHttpMessageConverter
long、int等类型使用MappingJackson2HttpMessageConverter转化器处理
String类型使用 StringHttpMessageConverter转化器处理
-
解决方案
在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); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!