Spring拦截器preHandle中ContentCachingRequestWrapper请求体为空排查
问题根因
ContentCachingRequestWrapper 没有主动预加载请求体的逻辑,内部缓存是被动填充的:只有当请求链路中其他组件(比如Controller层的@RequestBody参数解析器、业务代码主动调用getInputStream()/getReader()方法)读取过请求体后,它才会把读取到的内容存入内部的字节缓存数组。
不同拦截节点的表现差异原因:
preHandle是Controller方法执行前的拦截节点,此时请求体还未被任何下游组件读取,ContentCachingRequestWrapper内部缓存为空,因此调用getContentAsByteArray()只能拿到空内容。afterCompletion是整个请求处理全链路完成后才执行的节点,此时Controller已经完成请求体的解析读取,缓存已经被填充,因此可以正常获取到完整请求内容。
修复方法
如果需要在preHandle阶段就获取请求体内容,需要在自定义过滤器中包装完请求后,主动触发一次请求体读取,提前完成缓存填充,修改后的过滤器代码如下:
@Component public class CustomServletWrappingFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { ContentCachingRequestWrapper wrappingRequest = new ContentCachingRequestWrapper(httpServletRequest); ContentCachingResponseWrapper wrappingResponse = new ContentCachingResponseWrapper(httpServletResponse); // 主动读取请求体,提前写入缓存,不会影响后续组件重复读取 wrappingRequest.getInputStream().readAllBytes(); filterChain.doFilter(wrappingRequest, wrappingResponse); wrappingResponse.copyBodyToResponse(); } }
注意:Java 8及以下版本没有
readAllBytes()方法,可以手动通过字节数组循环读取流内容完成缓存初始化。该操作不会影响后续链路对请求体的读取,ContentCachingRequestWrapper会把首次读取的内容全部存入缓存,后续所有对请求输入流的读取操作都会直接从缓存中取数据,不会出现流被耗尽无法重复读取的问题。
内容的提问来源于stack exchange,提问作者Unknown_HHM
相关产品推荐
相关产品推荐

