Spring的过滤器获取请求体中JSON参数,同时解决Controller获取不到请求体参数的问题。

Spring的过滤器获取请求体中JSON参数,同时解决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 在后续处理请求时仍然可以读取到原始数据。

⁣⁣⁣⁣  ⁣⁣⁣⁣ 这里就需要用到 HttpServletRequestWrapperHttpServletRequest 的包装类) 。它允许我们 自定义方法 ,比如将请求体数据 提前读取并存储 ,然后 重写 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!到这里结束!!!

相关推荐
橘猫云计算机设计2 分钟前
基于SSM的《计算机网络》题库管理系统(源码+lw+部署文档+讲解),源码可白嫖!
java·数据库·spring boot·后端·python·计算机网络·毕设
菜鸟一枚在这41 分钟前
深度解析建造者模式:复杂对象构建的优雅之道
java·开发语言·算法
gyeolhada1 小时前
2025蓝桥杯JAVA编程题练习Day5
java·数据结构·算法·蓝桥杯
菜鸟一枚在这1 小时前
深入理解设计模式之代理模式
java·设计模式·代理模式
剑走偏锋o.O1 小时前
Spring MVC 框架学习笔记:从入门到精通的实战指南
学习·spring·springmvc
小天努力学java1 小时前
【面试系列】Java开发--AI常见面试题
java·人工智能·面试
river662 小时前
java开发——为什么要使用动态代理?
java
Zayn~2 小时前
JVM系列--虚拟机类加载机制
java
m0_748248022 小时前
Redis使用手册
java
CoderCodingNo2 小时前
【GESP】C++二级真题 luogu-b3924, [GESP202312 二级] 小杨的H字矩阵
java·c++·矩阵