Spring Boot 3实战案例锦集》PDF电子书已更新至130篇!

🎉🎉《Spring Boot实战案例合集》目前已更新213个案例,我们将持续不断的更新。文末有电子书目录。→ 现在就订阅合集


环境:Spring Boot 3.5.0



1. 简介

在 Spring Boot 中,你无法像下面这样直接多次使用 @RequestBody 注解来读取请求体内容:

@PostMappingpublic Map<String, Object> create(    @RequestBody User user, @RequestBody Address address) {  // ...}

Spring MVC 底层的实现机制决定了请求体(Request Body)只能被读取一次。这是因为 HTTP 请求体是一个输入流(InputStream),一旦被读取,流就会关闭,后续再次尝试读取时,流已经处于结束状态,无法再次获取数据。

要解决请求体只能读取一次的问题,通常我们会通过 Filter + 自定义 HttpServletRequestWrapper 的方式实现请求体的多次读取。详细的实现请查看下面链接:

SpringBoot接口参数如何使用多个@RequestBody?

本篇文章,我们将通过一种新的方式实现请求体的多次读取。

2.实战案例

2.1 自定义参数解析器

我们知道@RequestBody注解的参数会通过 RequestResponseBodyMethodProcessor 读取请求body内容。我们通过替换默认的实现多次读取body。

public class RepeatReadBodyMethodProcessor extends RequestResponseBodyMethodProcessor {  public RepeatReadBodyMethodProcessor(List<HttpMessageConverter<?>> converters,      List<Object> requestResponseBodyAdvice) {    super(converters, requestResponseBodyAdvice);  }   @Override  protected ServletServerHttpRequest createInputMessage(NativeWebRequest webRequest) {    HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);    Assert.state(servletRequest != null, "No HttpServletRequest");    return new RepeatBodyHttpRequest(servletRequest) ;  }}

这里我们实现重复读取请求body的核心是自定义的RepeatBodyHttpRequest对象:

public class RepeatBodyHttpRequest extends ServletServerHttpRequest {  private static final String REQ_BODY_CONTENT = "req_body_content" ;
  private HttpServletRequest request ;
  public RepeatBodyHttpRequest(HttpServletRequest request) {    super(request);    this.request = request ;  }
  @Override  public InputStream getBody() throws IOException {    byte[] body = (byte[]) this.request.getAttribute(REQ_BODY_CONTENT) ;    if (body != null) {      return new ByteArrayInputStream(body) ;    }    InputStream is = super.getBody() ;    body = StreamUtils.copyToByteArray(is) ;    this.request.setAttribute(REQ_BODY_CONTENT, body) ;    return new ByteArrayInputStream(body) ;  }}

我们将请求body缓存到当前的request对象中,这样下次再次读取body时先判断request中是否存在。

2.2 注册参数解析器

上面实现的自定义参数解析器,我们不能通过自定义 WebMvcConfigurer#addArgumentResolvers 方法注册,这种方式注册的参数解析器优先级没有系统默认的高。我们可以通过如下方式(不止这一种方式)

@Componentpublic class ConfigArgumentProcessor {  private final RequestMappingHandlerAdapter requestMappingHandlerAdapter ;  public ConfigArgumentProcessor(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {    this.requestMappingHandlerAdapter = requestMappingHandlerAdapter;  }
  @PostConstruct  private void init() {    List<HandlerMethodArgumentResolver> originResolvers = this.requestMappingHandlerAdapter.getArgumentResolvers() ;    List<HandlerMethodArgumentResolver> handlers = new ArrayList<>() ;    for (HandlerMethodArgumentResolver handler : originResolvers) {      if (handler instanceof RequestResponseBodyMethodProcessor) {        try {          Field field = RequestMappingHandlerAdapter.class.getDeclaredField("messageConverters") ;          field.setAccessible(true) ;          List<HttpMessageConverter<?>> messageConverters = (List<HttpMessageConverter<?>>) field.get(requestMappingHandlerAdapter) ;          field = RequestMappingHandlerAdapter.class.getDeclaredField("requestResponseBodyAdvice") ;          field.setAccessible(true) ;          List<Object> requestResponseBodyAdvice = (List<Object>) field.get(this.requestMappingHandlerAdapter) ;          handlers.add(new RepeatReadBodyMethodProcessor(messageConverters, requestResponseBodyAdvice)) ;        }      }      handlers.add(handler) ;    }    this.requestMappingHandlerAdapter.setArgumentResolvers(handlers) ;  }}

这样我们就替换了默认的实现,而对于原有的功能没有任何的影响。

2.3 测试

@RestController@RequestMapping("/repeatbody")public class RepeatBodyController {  @PostMapping  public Map<String, Object> create(@RequestBody User user,       @RequestBody Address address) {    return Map.of("user", user, "address", address) ;  }
  public static record User(String name, Integer age, String email) {}  public static record Address(String province, String city, String county) {}}

运行结果

图片

以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏

性能杀手!JPA 、MyBatis经典的N+1问题,正在拖垮你的系统

漂亮!5种验证码!Spring Boot 集成超强验证码,安全又高效!

API接口优化!Spring Boot 接口优化新思路,性能提升35%

@RequestBody已弃用!Spring Boot 一个注解实现任意JSON的读取

性能相差60倍!Spring Boot 批量插入你做对了吗?5种方式对比

Spring Boot +  iTextPdf:3步实现HTML模板生成pdf

太强了!Spring Boot 全方位的请求/响应日志记录【神级开源组件】

Spring Boot 二进制数据传输:从内存优化到流式处理与断点续传

new 的对象也能被 Spring 注入?AspectJ LTW 打破限制!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐