Spring中HttpServletRequest读取后内容为空的解决方案咨询
问题解决:Spring中多次读取HttpServletRequest请求体的方案
问题原因
HttpServletRequest的输入流是单向只读的,读完一次后指针就走到流的末尾了,后续再调用getInputStream()或者getReader()自然读不到内容——这就是Class A读取后Class B拿不到请求体的根本原因。
解决方案:自定义请求包装类缓存请求体
核心思路是提前把请求体缓存起来,让后续所有读取操作都从缓存中获取,而不是直接读原始输入流。具体步骤如下:
1. 编写CachedHttpServletRequest包装类
继承HttpServletRequestWrapper,把请求体缓存成字节数组,重写getInputStream()和getReader()方法,每次都从缓存的字节数组生成新的流:
import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; import java.nio.charset.StandardCharsets; public class CachedHttpServletRequest extends HttpServletRequestWrapper { private byte[] cachedBody; public CachedHttpServletRequest(HttpServletRequest request) throws IOException { super(request); // 读取原始请求体并缓存 InputStream requestInputStream = request.getInputStream(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = requestInputStream.read(buffer)) != -1) { byteArrayOutputStream.write(buffer, 0, bytesRead); } cachedBody = byteArrayOutputStream.toByteArray(); } @Override public ServletInputStream getInputStream() throws IOException { return new CachedServletInputStream(cachedBody); } @Override public BufferedReader getReader() throws IOException { ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody); return new BufferedReader(new InputStreamReader(byteArrayInputStream, StandardCharsets.UTF_8)); } // 自定义ServletInputStream实现,从缓存读取 private static class CachedServletInputStream extends ServletInputStream { private final ByteArrayInputStream inputStream; public CachedServletInputStream(byte[] cachedBody) { this.inputStream = new ByteArrayInputStream(cachedBody); } @Override public int read() throws IOException { return inputStream.read(); } @Override public boolean isFinished() { return inputStream.available() == 0; } @Override public boolean isReady() { return true; } @Override public void setReadListener(ReadListener readListener) {} } }
2. 编写Filter替换原始请求
创建一个Filter,在请求到达业务类之前,把原始请求替换成我们的缓存请求:
import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; @WebFilter(urlPatterns = "/*") // 可根据业务调整拦截路径 public class RequestBodyCacheFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (request instanceof HttpServletRequest) { HttpServletRequest httpRequest = (HttpServletRequest) request; // 只包装有请求体的请求方法,比如POST、PUT String method = httpRequest.getMethod(); if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) { CachedHttpServletRequest cachedRequest = new CachedHttpServletRequest(httpRequest); chain.doFilter(cachedRequest, response); return; } } chain.doFilter(request, response); } @Override public void init(FilterConfig filterConfig) throws ServletException {} @Override public void destroy() {} }
3. 注册Filter(可选)
如果项目没启用@WebFilter自动扫描,就在Spring配置类里手动注册:
import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<RequestBodyCacheFilter> requestBodyCacheFilter() { FilterRegistrationBean<RequestBodyCacheFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new RequestBodyCacheFilter()); registrationBean.addUrlPatterns("/*"); registrationBean.setOrder(1); // 确保这个Filter在其他需要读请求体的组件之前执行 return registrationBean; } }
4. 优化原有读取代码(可选)
现在请求体可以重复读取,原来的extractBody方法可以简化:
public static String extractBody(HttpServletRequest request) { try (BufferedReader reader = request.getReader()) { StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } return sb.toString(); } catch (IOException e) { throw new RuntimeException(e); } }
注意事项
- 缓存请求体会占用内存,处理大文件上传等场景时要注意内存限制,可添加请求大小判断逻辑。
- 调整Filter的拦截路径,避免对不需要读请求体的请求做包装。
- 如果用了Spring Security,要确保这个Filter的执行顺序在Security过滤器之前,否则Security读取请求体后还是会出现问题。
内容的提问来源于stack exchange,提问作者RedBlue
相关产品推荐
相关产品推荐

