springboot 解决过滤器 HttpServletRequest body IO流只能读取一次的问题
问题
老手都知道过滤器的作用,但是如果想要打印请求的参数就有点难度了, url 上拼接的参数可以轻而易举的得到,调用 request.getParameterMap() 方法就可以拿到;
但是要拿body 体就没这么简单了,request 里面有个 getInputStream(),这个流 是一次性的, 如果你在这里读出来,那么在控制层就拿不到body参数了;

错误方式:直接读取原始流打印(导致控制器无法获取)
如果在过滤器中直接调用原始请求的 getInputStream() 或 getReader() 打印,会直接消耗流,导致控制器后续读取时无数据:
// 错误示例:直接读取原始流,导致流被消耗
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// 直接读取原始流并打印(错误!)
String body = IOUtils.toString(httpRequest.getInputStream(), StandardCharsets.UTF_8);
System.out.println("Filter打印body:" + body);
// 此时流已被消耗,控制器无法读取
chain.doFilter(request, response);
}
方案
问题本质:输入流的 “一次性” 特性
ServletRequest 的输入流(getInputStream()/getReader())是单向流动的字节流,默认情况下,一旦读取(如在过滤器中打印),流的指针会移动到末尾,后续(如控制器)再读取时就会获取到空数据。
因此,问题的核心不是 “是否在过滤器中打印”,而是 “打印时是否消耗了原始流且未缓存”。
正确方式:通过包装类缓存后打印(控制器可正常获取)
如果先通过 HttpServletRequestWrapper 缓存流数据,再在过滤器中读取缓存的数据打印,原始流不会被消耗,控制器可通过包装类的缓存读取;
过滤器内容
@Component
@0rder(o)
@Slf4j
public class WebFilter extends OncePerRequestFilter {
@0verride
protected void dofilterInternal(HttpservletRequest request, HttpservletResponse response, Filterchain filterchain)throws servletException, I0Exception{
// 正确示例:基于包装类的缓存打印,不消耗原始流
if (request instanceof HttpServletRequest) {
// 先包装请求,缓存body
CachedServletRequestWrapper wrappedRequest = new CachedServletRequestWrapper((HttpServletRequest) request);
// 从包装类的缓存中读取并打印(不影响原始流)
String body = new String(wrappedRequest.getCachedBody(), StandardCharsets.UTF_8);
System.out.println("Filter打印body:" + body);
// 传递包装后的请求,控制器可从缓存中读取
chain.doFilter(wrappedRequest, response);
} else {
chain.doFilter(request, response);
}
}
CachedServletRequestWrapper.java
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
* 缓存请求体的包装类,解决ServletRequest输入流只能读取一次的问题
*/
public class CachedServletRequestWrapper extends HttpServletRequestWrapper {
// 缓存的请求体字节数组
private final byte[] cachedBody;
/**
* 构造方法:读取原始请求的输入流并缓存
*/
public CachedServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.cachedBody = readRequestBody(request);
}
/**
* 读取原始请求的输入流并转换为字节数组
*/
private byte[] readRequestBody(HttpServletRequest request) throws IOException {
// GET/DELETE等方法通常没有请求体,直接返回空数组
String method = request.getMethod();
if ("GET".equalsIgnoreCase(method) || "DELETE".equalsIgnoreCase(method)) {
return new byte[0];
}
// 读取输入流并缓存
try (InputStream inputStream = request.getInputStream();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, length);
}
return outputStream.toByteArray();
}
}
/**
* 重写getInputStream(),返回基于缓存的输入流
*/
@Override
public ServletInputStream getInputStream() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
@Override
public boolean isReady() {
return true; // 同步读取时始终就绪
}
@Override
public void setReadListener(ReadListener listener) {
// 异步场景可忽略,同步场景无需实现
}
};
}
/**
* 重写getReader(),基于缓存的输入流构建字符读取器
*/
@Override
public java.io.BufferedReader getReader() throws IOException {
return new java.io.BufferedReader(
new java.io.InputStreamReader(getInputStream(), StandardCharsets.UTF_8)
);
}
/**
* 提供获取缓存的请求体字节数组的方法(方便过滤器/拦截器直接使用)
*/
public byte[] getCachedBody() {
return cachedBody;
}
/**
* 提供获取缓存的请求体字符串的方法(简化使用)
*/
public String getCachedBodyAsString() {
return new String(cachedBody, StandardCharsets.UTF_8);
}
}
可以看到,正常打印了body数据

到此这篇关于springboot 解决过滤器 HttpServletRequest body IO流只能读取一次的问题的文章就介绍到这了,更多相关springboot HttpServletRequest body IO读取一次内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
SpringBoot3 整合Docker-Compose的实现步骤
本文主要介绍了SpringBoot3 整合Docker-Compose的实现步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2025-08-08
JDK自带监控工具jstat、jmap、jstack的使用指南(附命令示例)
jstat是JDK中提供的一个命令行工具,主要用来打印JVM 性能数据相关的统计数据,这篇文章主要介绍了JDK自带监控工具jstat、jmap、jstack的使用指南,文中通过代码介绍的非常详细,需要的朋友可以参考下2026-01-01
springboot中将日志信息存储在catalina.base中过程解析
这篇文章主要介绍了springboot中将日志信息存储在catalina.base中过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下2019-09-09
Spring Data JPA实现分页Pageable的实例代码
本篇文章主要介绍了Spring Data JPA实现分页Pageable的实例代码,具有一定的参考价值,有兴趣的可以了解一下2017-07-07


最新评论