Spring的过滤器获取请求体中JSON参数,同时解决Controller获取不到请求体参数的问题。
文章目录
- 前言
- 一、需求场景描述
- 二、原因解析
- [三、自定义 `HttpServletRequestWrapper` 来保存数据解决Controller获取不到的问题。](#三、自定义
HttpServletRequestWrapper
来保存数据解决Controller获取不到的问题。) - 四、案例(要注意的点)
前言
Spring的过滤器获取请求体中JSON参数,同时解决Controller获取不到请求体参数的问题。
一、需求场景描述
在我的开发的一个项目中,有一个需求就是将每个请求的参数,都记录在访问日志中,这样在出现问题时可以进行反查。
当时我就想去用过滤器来实现这个功能。 当请求来到过滤器 时,会有一个Request 参数,通过该参数就能获取到请求路径和请求参数等等相关内容。
对于GET请求、请求头参数,我们很容易去获取到,但是对于请求体,如POST请求传的JSON,那就没法获取到。我们需要去借助流,POST的请求是在请求体body中(body参数是以流形式存在的),我们可以通过获取到输入流来获取body,然后就可以获取到了!!!
c
// 获取请求体的输入流,读取请求体的内容
ServletInputStream inputStream = httpRequest.getInputStream();
// 使用 UTF-8 编码读取所有行并拼接成字符串
String bodyData = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))
.lines()
.collect(Collectors.joining("\n"));
//输出
System.out.println(bodyData);
通过上面的方法,我们确实能在过滤器filter中获取到POST的JSON参数了,但是按照上面的方法实现的过滤器, 我们会发现,当请求经过过滤器来到Controller的时候,你再去获取请求体里面的参数,就会一直报错!核心原因就是请求体body已经被销毁!
二、原因解析
根据IDEA的DeBug跟踪分析,大致如下:
Spring Boot 他是通过 获取 request
的输入流(InputStream)来读取请求参数 。InputStream
内部有一个 位置指针(position) ,用于标记当前读取到的位置。每次 读取数据 ,位置指针都会向前移动;当读取到末尾时,read()
方法会返回 -1
,表示数据已经读完。
如果想 重新读取 请求体数据,可以调用 inputStream.reset()
方法,这样位置指针会回到 上次调用 mark()
的位置 (默认是 0
),从而可以从头开始读取。
由于 request
的输入流 只能读取一次 ,如果在 过滤器中 读取了请求体数据,但没有 重置流 ,那么到 Controller 层 时,输入流就已经被消费完了,导致请求参数无法再次获取。

上面就是原因。
所以,在过滤器中如果要多次使用请求体数据,通常会 缓存请求体 ,比如使用 ContentCachingRequestWrapper
或者自定义 HttpServletRequestWrapper
来保存数据。
这里我采用的就是通过自定义 HttpServletRequestWrapper
来保存数据。
三、自定义 HttpServletRequestWrapper
来保存数据解决Controller获取不到的问题。
解决问题核心思路 :由于 InputStream
只能读取一次 ,如果在过滤器中读取了请求体数据,Spring Boot 后续就无法再次读取。
为了解决这个问题,我们可以 先把 InputStream
的数据缓存起来 ,然后将其 完整地传递下去,这样 Spring Boot 在后续处理请求时仍然可以读取到原始数据。
这里就需要用到 HttpServletRequestWrapper
(HttpServletRequest
的包装类) 。它允许我们 自定义方法 ,比如将请求体数据 提前读取并存储 ,然后 重写 getInputStream()
方法 ,确保后续仍然可以读取请求体数据。这样,无论是 过滤器 还是 Controller,都可以多次读取请求体,而不会丢失数据。
我的代码截图如下:
代码如下:
c
public class RequestWrapper extends HttpServletRequestWrapper {
private final byte[] body; // 用于缓存请求体数据的字节数组
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
// 读取请求体数据并转换为字节数组进行存储,防止InputStream被消费后无法重复读取
body = StreamUtils.copyToByteArray(request.getInputStream());
}
// 获取请求体的字符串内容
public String getBodyString() {
return new String(body, StandardCharsets.UTF_8);
}
@Override
public BufferedReader getReader() throws IOException {
// 返回包装后的 BufferedReader,使请求体可多次读取
return new BufferedReader(new InputStreamReader(getInputStream()));
}
@Override
public ServletInputStream getInputStream() throws IOException {
// 用已缓存的字节数组创建新的输入流,确保请求体可重复读取
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read(); // 读取字节数据
}
@Override
public boolean isFinished() {
return bais.available() == 0; // 判断是否读取完毕
}
@Override
public boolean isReady() {
return true; // 随时可以读取
}
@Override
public void setReadListener(ReadListener readListener) {
// 这里不做特殊处理
}
};
}
}
通过保存一份流,就可实现在过滤器中能拿到JSON参数,同时Controller也不会丢失参数!!!
四、案例(要注意的点)
下面是我的代码,对于业务进行简化了,方便大家阅读。

我这里用POST发起请求,控制台打印的如下:
这样大家就可以处理自己的业务了!!!
OK!到这里结束!!!