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

news/2025/2/23 23:40:26

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

文章目录

  • 前言
  • 一、需求场景描述
  • 二、原因解析
  • 三、自定义 `HttpServletRequestWrapper` 来保存数据解决Controller获取不到的问题。
  • 四、案例(要注意的点)


前言

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


一、需求场景描述



⁣⁣⁣⁣  ⁣⁣⁣⁣ 在我的开发的一个项目中,有一个需求就是将每个请求的参数,都记录在访问日志中,这样在出现问题时可以进行反查。
 ⁣⁣⁣⁣  ⁣⁣⁣⁣ 当时我就想去用过滤器来实现这个功能。 当请求来到过滤器时,会有一个Request参数,通过该参数就能获取到请求路径和请求参数等等相关内容。
 ⁣⁣⁣⁣  ⁣⁣⁣⁣  对于GET请求、请求头参数,我们很容易去获取到,但是对于请求体,如POST请求传的JSON,那就没法获取到。我们需要去借助流,POST的请求是在请求体body中(body参数是以流形式存在的),我们可以通过获取到输入流来获取body,然后就可以获取到了!!!

// 获取请求体的输入流,读取请求体的内容
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,都可以多次读取请求体,而不会丢失数据。

我的代码截图如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码如下:

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


http://www.niftyadmin.cn/n/5863869.html

相关文章

STM32的HAL库开发---多通道ADC采集(DMA读取)实验

一、实验介绍 1、功能描述 通过DMA读取数据 通过ADC1通道0/1/2/3/4/5(PA0/1/2/3/4/5)采集测试电压,并显示ADC转换的数字量及换算后的电压值 2、确定最小刻度 VREF 3.3V ---> 0V ≤ VIN ≤ 3.3V --->最小刻度 3.3 / 4096 &#x…

火语言RPA--Excel清空数据

【组件功能】:清空Excel内指定位置的内容 配置预览 配置说明 清空位置 单元格:清空指定单元格内容。 行:清空指定行内容。 列:清空指定列内容。 区域:清空一个区域内容。 行号 支持T或# 行号从1开始。 列名支持T或# 列名从…

【Day46 LeetCode】图论问题 Ⅳ

一、图论问题 Ⅳ 1、字符串接龙 采用BFS&#xff0c;代码如下&#xff1a;&#xff08;判断是否在字典中需要遍历每个位置&#xff0c;同时遍历26中可能有点不优雅&#xff09; # include<iostream> # include<string> # include<vector> # include<un…

HDFS Java 客户端 API

一、基本调用 Configuration 配置对象类&#xff0c;用于加载或设置参数属性 FileSystem 文件系统对象基类。针对不同文件系统有不同具体实现。该类封装了文件系统的相关操作方法。 1. maven依赖pom.xml文件 <dependency><groupId>org.apache.hadoop</groupId&g…

编译部署使用腾讯云cpp-cos-sdk

创建对象存储 在腾讯云上创建对象存储,再创建一个子用户,对他进行授权; 点击我们创建的子账户,在其中新建一个密钥 创建存储桶 然后到控制台上创建一个存储桶用于测试 编译sdk 我们需要去windows上编译sdk源码,在对象存储的控制台中常用工具中有sdk下载:里面有一个c++…

【C】队列与栈的相互转换

栈与队列是两种特点相反的数据结构&#xff0c;一个特点是后进先出&#xff0c;一个特点是先进先出&#xff0c;但是他们之间是可以相互转换的。 目录 1 用队列实现栈 1&#xff09; 题目解析 2&#xff09; 算法解析 &#xff08;1&#xff09; 结构(MyStack) &#xff…

SQLMesh 系列教程8- 详解 seed 模型

在数据分析和建模过程中&#xff0c;外部模型&#xff08;External Models&#xff09;在 SQLMesh 中扮演着重要角色。外部模型允许用户引用外部数据源或现有数据库表&#xff0c;从而实现灵活的数据整合和分析。本文将介绍外部模型的定义、生成方法&#xff08;包括使用 CLI 和…

线性模型 - Softmax 回归(参数学习)

本文&#xff0c;我们来学习Softmax 回归的参数学习&#xff0c;在开始之前&#xff0c;我们先了解一下“损失函数”、“风险函数”和“目标函数”这三个核心概念。 一、损失函数、风险函数、目标函数 1. 损失函数&#xff08;Loss Function&#xff09; 定义&#xff1a; 损…