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!到这里结束!!!

相关推荐
Mryan2005几秒前
解决GraalVM Native Maven Plugin错误:JAVA_HOME未指向GraalVM Distribution
java·开发语言·spring boot·maven
inxunoffice2 分钟前
按规则批量修改 txt/html/json/xml/csv/记事本等文本文件内容
xml·json
VX_CXsjNo111 分钟前
免费送源码:Java+SSM+Android Studio 基于Android Studio游戏搜索app的设计与实现 计算机毕业设计原创定制
java·spring boot·spring·游戏·eclipse·android studio·android-studio
ylfhpy16 分钟前
Java面试黄金宝典33
java·开发语言·数据结构·面试·职场和发展·排序算法
乘风!33 分钟前
Java导出excel,表格插入pdf附件,以及实现过程中遇见的坑
java·pdf·excel
小小鸭程序员44 分钟前
Vue组件化开发深度解析:Element UI与Ant Design Vue对比实践
java·vue.js·spring·ui·elementui
南宫生1 小时前
Java迭代器【设计模式之迭代器模式】
java·学习·设计模式·kotlin·迭代器模式
seabirdssss2 小时前
通过动态获取项目的上下文路径来确保请求的 URL 兼容两种启动方式(IDEA 启动和 Tomcat 部署)下都能正确解析
java·okhttp·tomcat·intellij-idea
kill bert2 小时前
第30周Java分布式入门 消息队列 RabbitMQ
java·分布式·java-rabbitmq
穿林鸟3 小时前
Spring Boot项目信创国产化适配指南
java·spring boot·后端