關於Spring中RequestBodyAdvice的使用

這是我參與8月更文挑戰的第12天,活動詳情查看:8月更文挑戰java

博主最近在使用註解功能時, 遇到關於Spring中關於RequestBodyAdvice使用,記錄一下.mysql

1 關於RequestBodyAdvice概述

/** * Allows customizing the request before its body is read and converted into an * Object and also allows for processing of the resulting Object before it is * passed into a controller method as an {@code @RequestBody} or an * {@code HttpEntity} method argument. * * <p>Implementations of this contract may be registered directly with the * {@code RequestMappingHandlerAdapter} or more likely annotated with * {@code @ControllerAdvice} in which case they are auto-detected. * * @author Rossen Stoyanchev * @since 4.2 */
public interface RequestBodyAdvice {

	boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType);


	Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType);

	HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;

	Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType);

}
複製代碼

如上面源碼能夠得知,該接口是Spring4.2版本新增的接口.查閱資料得知,該接口主要是用於給請求體參數作先後加強處理的.spring

查看一下源碼中的beforeBodyRead方法,看到RequestResponseBodyAdviceChain類有實現該接口,並調用該方法,繼續向下查看,發現AbstractMessageConverterMethodArgumentResolver抽象類中readWithMessageConverters方法中調用該方法.該抽象類實現了HandlerMethodArgumentResolver處理方法參數接口.sql

...
for (HttpMessageConverter<?> converter : this.messageConverters) {
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				if (converter instanceof GenericHttpMessageConverter) {
					GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
					if (genericConverter.canRead(targetType, contextClass, contentType)) {
						if (logger.isDebugEnabled()) {
							logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
						}
						if (inputMessage.getBody() != null) {
                            // 調用接口下的實現方法
							inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
							body = genericConverter.read(targetType, contextClass, inputMessage);
							body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
						}
						else {
							body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
						}
						break;
					}
				}
...
複製代碼

該接口方法,主要是HttpMessageConverter處理Request Body的先後作一個參數處理.markdown

AbstractMessageConverterMethodArgumentResolver抽象類的實現子類有app

  • HttpEntityMethodProcessor 處理controller的方法參數是HttpEntity或RequestEntity的)
/** * Resolves {@link HttpEntity} and {@link RequestEntity} method argument values * and also handles {@link HttpEntity} and {@link ResponseEntity} return values. **/
複製代碼
  • RequestPartMethodArgumentResolver 處理方法參數是@RequestPart和MultipartFile和Part
/** * Resolves the following method arguments: * <ul> * <li>Annotated with {@code @RequestPart} * <li>Of type {@link MultipartFile} in conjunction with Spring's {@link MultipartResolver} abstraction * <li>Of type {@code javax.servlet.http.Part} in conjunction with Servlet 3.0 multipart requests * </ul> **/
複製代碼
  • RequestResponseBodyMethodProcessor 處理方法參數是RequestBody
/** * Resolves method arguments annotated with {@code @RequestBody} and handles return * values from methods annotated with {@code @ResponseBody} by reading and writing * to the body of the request or response with an {@link HttpMessageConverter}. **/
複製代碼

從上面可知, 在使用HandlerMethodArgumentResolver接口時,能夠對請求的參數進行解析前處理,解析後處理.且開啓參數處理功能的開關在於重寫的supports方法返回爲True才行.ide

2 關於RequestBodyAdvice的使用

1 準備環境

搭建一個能夠運行的SpringBoot環境.工具

1 準備文件

1 application.yml

server:
  port: 8081
spring:
  datasource:
    driverClassName: com.mysql.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/test
複製代碼

2 實體類

@Data
public class User {

    private String id;

}
複製代碼

3 Controller控制器

@RestController
@RequestMapping("/consumer")
@Slf4j
public class ConsumerController {

    @RequestMapping("/query")
    public String queryById(@RequestBody User user) {
        log.info("請求參數=={}", user.toString());
        log.info("響應參數=={}", "id的h1樣式");

        return "<h1>" + user.toString() + "<h1>";
    }

}
複製代碼

2 建立一個攔截類,實現RequestBodyAdvice接口

@ControllerAdvice
@Slf4j
public class LogRequestBodyAdvice implements RequestBodyAdvice {
	
    // 是否開啓攔截 true開啓 false不開啓
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return true;
    }

    /** * 請求體解析前處理 * @param httpInputMessage * @param methodParameter * @param type * @param aClass * @return * @throws IOException */
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {

        // 常見的業務倉場景有: 1 記錄日誌 2 內容加密解密 3 是否開啓分頁功能
        log.info("攔截到的請求參數爲 = {}",methodParameter.toString());
        Method method=methodParameter.getMethod();
        log.info("請求體讀取前={}==>{}==>{}==>{}",method.getDeclaringClass().getSimpleName(),method.getName(),type.toString(),aClass.getName());
        return httpInputMessage;
    }

    /** * 請求體解析後處理 * @param o * @param httpInputMessage * @param methodParameter * @param type * @param aClass * @return */
    @Override
    public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        Method method=methodParameter.getMethod();
        log.info("請求體讀取後={}.{}:{}",method.getDeclaringClass().getSimpleName(),method.getName(),o.toString());
        return o;
    }

    /** * 處理沒有參數 * @param o * @param httpInputMessage * @param methodParameter * @param type * @param aClass * @return */
    @Override
    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        Method method=methodParameter.getMethod();
        log.info("沒有參數={}.{}",method.getDeclaringClass().getSimpleName(),method.getName());
        return o;
  }
複製代碼

3 使用postman工具測試

1 postman發送請求

image-20210811081913533

2 項目日誌

image-20210811081946426

3 總結

對於請求體參數解析前,解析後的攔截,能夠更好的幫助實現業務功能,而對代碼邏輯沒有入侵.常見的使用場景有以下等:post

  • 1 記錄日誌測試

  • 2 內容加密解密

  • 3 是否開啓分頁功能

相關文章
相關標籤/搜索