-
作用:
允許在執行標有@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); } }