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;
				}
			}
		}
  • 源碼分析
  1. 遍歷this.messageConverters,識別到converter轉換器是StringHttpMessageConverter

  2. 調用自定義的beforeBodyWrite方法,得到body類型是Resp類型數據

  3. 采用StringHttpMessageConverter處理Resp類型的數據,導致類型轉換異常

  1. MappingJackson2HttpMessageConverter、StringHttpMessageConverter

    long、int等類型使用MappingJackson2HttpMessageConverter轉化器處理

    String類型使用 StringHttpMessageConverter轉化器處理

  2. 解決方案

    在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);
        }
    
    }
    


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM