You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用ContentCachingRequestWrapper缓存请求后,如何读取控制器请求体?

为什么用ContentCachingRequestWrapper包装后,@RequestBody还能正常读取请求体?

这是个很常见的疑问,核心在于你对ContentCachingRequestWrapper的工作流程和Spring MVC的请求处理逻辑有点误解,我来给你拆解清楚:


1. 过滤器并没有提前消耗请求流

你在过滤器里做的只是把原始的HttpServletRequest包装成ContentCachingRequestWrapper,这个过程并没有调用getReader()或getInputStream()方法——也就是说,请求流此时完全没有被读取过,自然不存在“流已被消耗”的问题。真正触发请求体读取的操作,是Spring MVC在处理@RequestBody注解参数的时候。

2. ContentCachingRequestWrapper的核心是“缓存”而非“提前读取”

这个Wrapper的设计目的,就是解决Servlet请求流只能读取一次的痛点。它的工作逻辑是:

  • 当第一次调用getReader()/getInputStream()时,它会读取原始请求流的内容,同时把内容缓存到内部的字节数组中
  • 之后任何再次调用这两个方法的请求,都会直接从缓存的字节数组中返回数据,而不会再去读取原始流

这样一来,无论是Spring MVC处理@RequestBody,还是你后续在异常处理中要记录请求体,都能正常获取到请求内容。

3. 验证:在异常处理中读取缓存的请求体

比如你可以在全局异常处理器中这样获取并记录请求体:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(YourCustomException.class)
    public ResponseEntity<ErrorResponse> handleCustomException(HttpServletRequest request) throws IOException {
        // 把request强转为ContentCachingRequestWrapper
        ContentCachingRequestWrapper cachedRequest = (ContentCachingRequestWrapper) request;
        // 从缓存中获取请求体字节数组,转成字符串
        String requestBody = new String(cachedRequest.getContentAsByteArray(), cachedRequest.getCharacterEncoding());
        
        // 这里可以把requestBody写入日志
        log.error("自定义异常发生,请求体:{}", requestBody);
        
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(new ErrorResponse("处理失败", requestBody));
    }
}

简单总结:你的过滤器只是给请求加了个“缓存buff”,并没有提前消耗流;Spring MVC处理@RequestBody时会触发第一次读取并缓存,后续的任何读取操作都走缓存,所以两者完全不冲突~

内容的提问来源于stack exchange,提问作者Ankit Arora

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:12:22