java接口params参数获取不到_记一次getParameter()获取不到参数问题的排查

本文记录了一次在SpringBoot项目中遇到的接口参数获取异常问题,即通过httpServletRequest.getParameter()无法获取POST请求的参数。问题排查过程中发现,自定义的RequestWrapper对象和全局过滤器在获取请求体后影响了getParameter()的执行。经过分析,发现Tomcat的ServletRequest中,一旦调用了getInputStream()或getReader(),getParameter()将无法正常工作。最终发现问题源于新增的WebMvcConfigurationSupport配置,它阻止了WebMvcAutoConfiguration的加载,导致HiddenHttpMethodFilter过滤器未生效,而该过滤器会尝试通过getParameter()获取参数。解决方案是去掉WebMvcConfigurationSupport,改用WebMvcRegistrations来保持与老版本的一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这不是简单的获取不到参数的问题.

一、问题背景

项目使用springboot+mybatis, 是一个后台系统. 其中有一些功能:

有一个自定义的全局过滤器DecryptFilter, 用于接口的解密与验签

有一个自定义的全局过滤器LogFilter, 用于打印所有接口的请求参数与响应内容.

因此我们自定义了一个RequestWrapper对象包装了原本的HttpServletRequest, 并将其中的InputStream保存起来, 方便后面的代码去获取请求体或者请求流.

基于此背景的项目, 在新版本的迭代中, 一个表单提交的POST接口的处理便出现了问题.

二、问题现象.

问题的现象不仅在新版本中, 老版本也有. 只不过没引起注意, 在这次的问题暴露出来才排查到的.

在新的迭代中, Service层的逻辑代码中, 通过httpServletRequest.getParameter()获取参数为空

在新的迭代中, 全局过滤器LogFilter打印了请求体的内容, 与调用方调用时的请求参数一致(不为空).

在老版本中, 全局过滤器LogFilter打印的请求体内容为空, 但是httpServletRequest.getParameter()获取参数却是正确的.

三、分析思路

首先第一反应就想到:

是不是迭代的时候改到了全局过滤器或者自己封装的RequestWrapper对象, 导致后面再通过getParameter()就获取不到.

那么对比这几个关键的代码发现并没有任何改动, 并且在老版本中getParameter是可以获取到参数的. 因此这个猜想暂时的排除.

那接下来根据现象, 之前打印请求体是空, 现在又可以打印出请求体内容(GET请求只会打印QueryString, 其他请求只会打印请求体), 自然想到:

会不会是调用方修改了调用方式, 原来是将请求参数拼接到URL后面, 现在将请求参数放到请求体里面去啦?

这个猜想明显不能说明为什么新版本使用getParameter获取参数, 但还是求证了一下. 查看Nginx的访问日志便可知道, 之前的调用是否是将参数拼接在URL之后. 结果自然是没有.

查看getParameter的方法说明如图:

96d803fe17dc0ef964e9173948e9a3df.png

里面有重要的一点说明, 就是如果在POST请求中, 你直接使用getInputStream或getReader来获取请求体的话, 会对getParameter方法的执行造成影响. 但是什么影响呢, 目前并不清楚.

没有其他思路, 那便模拟接口调用, 在本地进行DEBUG看看问题出现在哪里.

在对新老版本分别DEBUG的时候, getParameter方法的执行路径在RequestWrapper中都一摸一样符合预期, 也进一步佐证了不会是第一个猜想. 然后在专门对新版本进行DEBUG时发现了一些有趣的现象

在全局过滤器的第一行进行getParameter操作, 发现是可以获取到参数的.

慢慢调试发现, 在生成RequestWrapper对象之后进行getParameter就获取不到参数了.

并且如果在new RequestWrapper之前进行了getParameter操作, 那么在后续的Service层中, 也可以获取到参数了. 但是RequestWrapper中获取到的原始InputStream就为空了

如果是在new RequestWrapper之后进行的getParameter操作时, 其中获取原始的InputStream不为空.

但是new RequestWrapper()里面啥也没做呀, 就只有关键一行代码如下:

body = StreamUtil.readBytes(request.getReader(), "UTF-8");

而且老版本也是一样的代码呀, 却可以正常获取到参数值呀.

带着疑问继续DEBUG到getParameter内部具体实现, 到了org.apache.catalina.connector.Request这个类中, 如下图:

aebee091cb8cd665ce08efa51da3600b.png

解释就是, 如果参数没有被解析, 那么就去解析, 否则就直接获取. OK那么继续DEBUG发现当时是未解析的, 因此就进入到了详细的参数解析中, 前一部分代码如图:

291fe48981a8e313e854251432485e65.png

结果呢, 在执行到红框那部分代码时, 便直接return了, 难怪没有参数呢. 那为啥直接return不继续解析了呢. 那就看看usingInputStream和usingReader到底干啥的.

跟踪这两个变量值发现:

只有在getInputStream()方法中, usingInputStream=true. 而只有在getReader()方法中, usingReader=true.

那这就表明, 只要你事先调用过了getInputStream或者getReader, 再调用getParameter就不会进行解析了,也就解释了为啥获取不到参数.

所以还真的是RequestWrapper的错!? 那为啥老版本没有问题??

其实老版本也是有问题的, 像最开始所说的, 只是没有引起注意而已:

在老版本中, 全局过滤器LogFilter打印的请求体内容为空, 但是httpServletRequest.getParameter()获取参数却是正确的.

那就要找到为啥老版本不打印参数了. 现在就开始DEBUG老版本.

首先, 同样在全局过滤器的第一行获取请求体进行打印, 发现......getReader()获取到的是空的. 我第一行打印都是空的是怎么回事?!!之前都执行过啥什么!

然后, 看了一眼DEBUG的栈调用路径, 发现调用路径与新版本不一样啊, 执行的一些Spring的过滤器也不一样. 为啥突然调用路径都变化了?

接着, 由于没有好的解释, 只能使用排除法, 切换回DEBUG新版本, 优先还原配置相关的改动一点点的试.

最后, 在我将新增加的WebMvcConfigurationSupport配置注释掉之后, 发现新版本的表现与老版本一致了.将WebMvcConfigurationSupport作为关键词搜了一下, 发现这货居然关联着WebMvcAutoConfiguration这个自动配置.

f8e3920732eee72f89b0b91b89e01160.png

Springboot只要加了这货, 就不会加载WebMvcAutoConfiguration, 看来自动配置还是坑多呀. 而且新版本缺少的正是上图下面框起来的过滤器.

那这些默认自动配置的过滤器为啥就能让getReader为空呢? 找到HiddenHttpMethodFilter这个过滤器, 发现了下面的代码

58c6bb0a5845101a3a048aa353ccca3c.png

原来这个过滤器里面执行了一次getParameter(). 这下所有问题都能解释通了.

四、总结

Tomcat的ServletRequest中, getParameter()方法与getInputStream()/getReader()不兼容, 只能选择一方.调用了一方, 另一方就会是空的.

WebMvcConfigurationSupport配置会导致WebMvcAutoConfiguration不加载

WebMvcAutoConfiguration中会加载一些配置, 可能影响你的一些行为.

最后, 为了降低影响跟老版本保持一致, 还是采用了WebMvcAutoConfiguration, 去掉了新增的WebMvcConfigurationSupport, 使用了WebMvcRegistrations来作为代替.

最后有两点疑问:

为什么使用@RequestBody或者@RequestParam可以获取到请求参数

Servlet为什么会将请求体保存在流中,只能读一次? 这样多次读取请求体都不方便. 为什么不直接存在一个字节数组中?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值