关于Spring中RequestBodyAdvice的使用

2,904 阅读3分钟

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战

博主最近在使用注解功能时, 遇到关于Spring中关于RequestBodyAdvice使用,记录一下.

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版本新增的接口.查阅资料得知,该接口主要是用于给请求体参数做前后增强处理的.

查看一下源码中的beforeBodyRead方法,看到RequestResponseBodyAdviceChain类有实现该接口,并调用该方法,继续向下查看,发现AbstractMessageConverterMethodArgumentResolver抽象类中readWithMessageConverters方法中调用该方法.该抽象类实现了HandlerMethodArgumentResolver处理方法参数接口.

...
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的前后做一个参数处理.

AbstractMessageConverterMethodArgumentResolver抽象类的实现子类有

  • 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才行.

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 总结

对于请求体参数解析前,解析后的拦截,可以更好的帮助实现业务功能,而对代码逻辑没有入侵.常见的使用场景有如下等:

  • 1 记录日志

  • 2 内容加密解密

  • 3 是否开启分页功能