spring boot中request的getParameterMap坑
简介
使用spring boot 的时候,通常会拦截请求中的参数在使用Filter拦截Request的时候,除了要注意body是一个stream,读取了一次要重新设置一个stream以外,鲜有人提到拦截前后getParameterMap方法的行为会产生变化
问题重现
这是Controller的代码,获取到parameterMap后返回
正常情况
在Filter中构造的Request不获取请求的body,也不重新设置body的stream
我们在Controller中调用getParameterMap()得到请求参数,将会是如下结果:
- url里的参数获取正常
- form里的参数获取正常
异常情况
在Filter中构造的Request中获取请求的body,重新设置body的stream,在获取body前不调用getParameterMap方法
我们在Controller中调用getParameterMap得到请求参数,将会是如下结果:
- url里的参数获取正常
- form里的参数获取不正常
但是如果在获取body前调用getParameterMap方法
将会是如下结果:
- url里的参数获取正常
- form里的参数获取正常
原因探究
直接在源码上断点,注意有时候反编译的源码会不匹配,在反编译出来的文件上点Download Source Code,会下载真正的源码进而匹配
从getParameterMap()函数看进去的话,经过几个函数,可以在org.apache.catalina.connector.Request类中看到getParameterMap()函数如下:
这里的代码说明,如果获取了一次map,之后就不用再获取了,所以只要第一次获取的是正确的,以后都会是正确的
问题在于,为什么我们不先获取map,但是直接在controller里面获取就不对了呢?
继续看就会进入Request类的getParameterNames()函数、parseParameters()函数
3183行的parameters.handleQueryParameters()处理了请求里的参数,此时如果usingInputStream是true,就return了,而3202行才处理了form里的参数
继续追踪usingInputStream的设值,发现只有一个地方,那就是getInputStream()函数!
所以一旦在调用getInputStream()前没有调用过getParameterMap()的话,form里的参数就会丢失
小结
这个问题是在做支付宝支付回调时,参考支付宝提供的demo里的代码,发现在controller里使用getParameterMap()获取不到参数,结合支付宝文档里说异步通知 header 是 Content-Type:application/x-www-form-urlencoded; text/html; charset=utf-8,就开始用form表单测试,最后得出结论,一定要在getInputStrem()前调用一次getParameterMap()