spring boot2 (五)web中获取请求参数及原理

我反正开发三年多,最常用的还是正常参数的形式传递获取。不过这个路径传参据说是restful风格的常用方式(我是个土鳖,restful风格也没实际用过)。
所以针对多种传参方式,这里也简单的记录一下:

路径传参

这个不仅仅是restful风格可以用,其实本质就是从路径的某段中获取参数,只要使用一个注释:

@PathVariable

下面是简单的使用 demo:


路径传参方法

然后我们去接口访问这个方法:


访问结果

事实证明,路径上带参数,我们用@PathVariable注解获取到了。
至于最后一个获取map的用法,其实在注解中是有说到的。
获取所有路径传参,用map<String,String>类型

获取请求头信息

这个其实也挺有用的,很多时候token都是放在请求头的,当然了还有别的东西也都ok。而获取请求头的方法除了我们在方法中用request获取,也可以直接获取。

@RequestHeader

其实这个方法和上面那个差不多。可以指定获取,也可以用map获取全部的。如下demo:


获取请求头全部信息

虽然看上去不怎么好看,但是起码证明确实获取到请求头了。这个就过了。

获取参数

这个就是正常参数了,依然一个注解:

@RequestParam

使用方法和上面一样,指定参数名称,或者获取全部参数(全部参数的话必须是String,String 的)。


获取全部参数

获取cookie

依旧一个注解开始:

@CookieValue

需要注意的是这个cookie是没有map的,看官网文档的说法,要用cookie接收:


cookie类型

因为我没前端页面都,所以这个访问要用postman来添加cookie再测试:


image.png

cookie的接收测试完毕。

常用的就这几个,接下来重点说参数获取的原理(这里用debug一步一步调试):

首先我们的测试代码是这样的:


请求

其实这个是继续上文说找到那个请求处理器以后的发展了,上文说通过DispatcherServlet类的getHandler方法,找到url所对应的方法全名称。这里仍然debug一步一步走:


走到这个方法

这里有一个小窍门:一般带注释的方法都是值得一看的。我是习惯性百度翻译一下看看的。然后继续往下走到下一个方法:
走到这个方法,进入方法中

ps:这里很多走到接口,然后进入实现方法的。反正见到方法就进入就行了


走到这个方法

其实这个类应该是个很重要的类,毕竟大量invoke方法。然后我们一步一步往下走:


下一步就走到这个方法

因为我这个方法就是普通方法,所以走到这个方法中。其实从它的名字中午文翻译:反射 处理器 方法。大概可以猜测是方法反射的处理器。
点进去以后继续一步一步走:


进入方法中

这个方法中代码很多,但是首先我觉得set本身不会存在赋值行为,所以说前面所有的set我都一路往下。最终走到一个看名字就高大上是方法:
看名字就觉得是有用的方法

然后点进这个方法:


点进这个方法中第一行代码又进入一个invoke的方法,所以再点进去

往里走接下来我手欠走过了,如下截图:
参数已经找到了

到我当前断点的位置,参数都已经赋值了,因为我断点已经过了, 所以直接看看走过的代码:
给参数赋值的代码

其实这个代码逻辑还算是简单,首先创建了一个参数长度的Object数组用来存参数。然后还是一个个 去找参数的值(中间用到了参数解析器)。找到以后continue,去找下一个参数。这里有一点要注意:找不到的话,会报错的!就是我常见的那个错:


找不到参数报错

哎,因为断点跟丢了现在好难受,我从新走一遍吧。
继续上面说的,给参数赋值的方法如下:


给参数赋值方法

点进去查看:
实现赋值方法

继续点进去:


判断当前参数类型是否支持

这个就用到了我们上面说的spring boot自带的26个参数类型解析器。可以点进去瞅瞅:
是一个方法的是否为空的判断

进方法中看:
方法就是一个简单的for循环比对

这里重点的是我圈起来的:argumentResolvers这个参数就是spring boot默认的所有解析器的集合。
如果这里返回null说明当前参数类型不支持,所以为空,最终出去就直接报错了。
spring boot不支持的参数类型就在这里报错

当有这个类型以后去赋值,继续往下走到了赋值的方法:
赋值方法

其实这里又查找了下,确定是有这个类型的。继续往下走:


26种参数解析器类型
根据类型不同去找对应的解析器的方法

其实这里很有意思,大家可以注意这个解析器和注解的名称密切相关啊。比如我demo中用的两个注解:一个@PathVariable一个@RequestParam。都分别有map和普通单个的解析。而且大多数都挺见名知意的,有空可以详细琢磨琢磨这26个解析器。

这里单独说一下我们的实体类是怎么解析的。正常来讲传一个实体对象,然后debug就行了,我这里直接说结果了:
实体类的参数解析器是这个:ServletModelAttributeMethodProcessor
接下来咱们在代码中一步一步走:


测试接口

debug代码走向:


首先这是作为一个参数传过来的

然后我们看看哪个类型解析器解析了这个实体类参数:
for循环中比对

这个因为类型解析器有26个,比较多。所以debug 的时候千万要看好。我就跟丢两次。然后有些一看就不是的注解就不用进去了。比如这个第一个@RequestParam类型的,绝对不可能直接往下走,省的耽误时间。我直接说比对完是哪个类型了:ServletModelAttributeMethodProcessor。我们可以看下走进去的代码:
解析器类型:ServletModelAttributeMethodProcessor

然后我们走进去看看是怎么绑定属性的:


首先获取参数名称

这个方法我也是跟着走了好几次代码。。这里调试一定要慢,宁可每个方法都进去走无用功也别贪快错过去。
反正遇到方法就进

这里主要用反射创建这个类的实例
反射创建这个类的实例

这个时候这个实例是空的,里面没有任何值:
反射创建出来的实例是空的

然后重点来了,赋值的方法是这:bindRequestParameters(binder, webRequest);我来回调试了三四次才确定了这么一个方法。执行前attribute没值,执行后有了。。一眨眼就错过:
这个方法很重要

反正是点点点进到了这个方法,获取实体类的属性并赋值
获取实体属性

从doBind方法进入,然后再进入父类的doBind方法:
父类的doBind方法

到这里可以一步一步进入看了,也算做到了见名知意(虽然我是进了方法才知道干嘛用的)。简单说下,第一步看有没有不许出现的属性。第二步需要的属性。


检查不许出现的属性

这个方法是比对属性是否可以传。我们可以设置实体对象种某些属性不能这么传入。如果不能传的属性现在传过来就会报错:
百度翻译这个报错信息

第二步检查需要的属性:
因为我这个类没设置,所以需要的是空,直接返回了

第三步走进来赋值:
赋值

这一步也会报一个常见的错:xxx属性不存在

就是这一步把实现了给attribute赋值。
再出来已经赋完值了

接下来我们继续往下走:
这里有个判断,attrubute能不能转化成参数类型

这里只要数据类型是对的,一定是可以转化的吧。我暂时没走过不是的分支。。
最后一直往后走,return attribute,然后我们的参数已经是这个对象了。就完事了。

其实这里可能是因为框架完善的原因,很多设计是为了扩展性或者维护性啥。所以debug的时候要不断父子类方法跳,而且还有我上面说的都是大致走向,很多细节还有重要的方法都没怎么说。总结一下我对这块的理解吧:

  1. 获取参数个数。
  2. 判断参数是不是spring boot能解析的类型(这里涉及到spring boot默认的解析器)。
  3. 获取参数上的注释和指定名称(如果指定名称的参数不存在则报错)。
  4. 将参数名称和实际值对应起来。
  5. 有些复杂类型数据用web数据绑定器,将请求参数的值绑定到指定的JavaBean里面。WebDataBinder 利用它里面的 Converters 将请求数据转成指定的数据类型。再次封装到JavaBean中。
  6. 所有的参数都这么走一遍。
    然后就over了。说起来简简单单,各种断点跳转方法几十个。来回走经常容易丢。。真的佩服写spring 的大佬。
    本篇笔记就到这里,如果稍微帮到你了记得点个喜欢点个关注,也祝大家工作顺顺利利!另外如果文中哪里表述不严瑾或者有问题欢迎指出!

推荐阅读更多精彩内容