请求处理-【源码分析】-Rest映射及源码解析
1、restful风格回顾
之前使用不同的url表示不同的操作,这些操作都只能使用get 或者 post两种请求方式,并且这些区分不同操作的取决于url,而不是请求方式!例如:/getuser获取用户、/deluser删除用户、/moduser修改用户、/saveuser保存用户;
但是如果使用rest风格可以将同一个url用不同的请求方式区分,然后根据不同的请求方式执行不同的操作;也就是说针对同一个url,不同的请求方式做不同的处理。例如:地址发起/user请求,由于请求方式的不同,GET-获取用户、 DELETE-删除用户 、PUT-修改用户、 POST-保存用户。
因此Rest风格支持(使用HTTP请求方式动词来表示对资源的操作)
备注:Rest风格的核心Filter过滤器:HiddenHttpMethodFilter
2、SpringBoot的rest风格用法
用表单进行rest风格各种请求的演示
2.1、页面index.html【首页】参数
1、开启页面表单的Rest功能,放置在静态资源static
目录下或者template
模板下
页面中需要使用表单提交,且提交非get/post请求时需要提交一个隐藏域_method
=put、delete等(如果直接get或post,无需隐藏域),method属性为method=post,value属性为请求方式,这样提交非get/post请求后会解析隐藏域,将post请求转化为对应的请求value值。
put/delete
请求为什么必须有一个隐藏域_method
答:springmvc源码自动配置类代码的HiddenHttpMethodFilter
类中,就是说明与该与redtful
风格有关,里面有属性_method
的介绍
2.2、编写控制器
说明:
- 控制层所有的path路径全都是一样的,但是需要给@RequestMapping注解设置属性处理的方式。
- @ResponseBody表示直接响应到
index
页面中不经过视图处理器。
2.3、application.yaml配置
需要在配置文件开启rest风格的允许,否则依旧是不生效的。这几个步骤缺少其中的任何一个,rest风格都不会生效
为什么要进行该配置?
答:springmvc源码配置类中第一个类HiddenHttpMethodFilter
,就是说明与该与redtful风格有关,如下,SpringBoot底层【版本2.5.4】默认是未开启restful风格的功能的。
2.4、测试
浏览器输入loclhost:8080
,就会默认跳转到首页【这部分功能以前讲过】,然后进行相应命令的测试并返回相应的数据到页面即可成功
2.5、Restful风格的RequestMapping注解简化
请求映射格式:@xxxMapping;
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
因此在restful风格加持下,可以将该注解简化写成下面格式
3、Rest映射原理(基于表单提交要使用REST的时候)
rest映射主要是通过post请求提交并且携带隐藏域(_method
),然后由HiddenHttpMethodFilter
类将其转换。
SpringBoot启动就自动装配WebMvcAutoConfiguration
类,然后由该类进行条件装配OrderedHiddenHttpMethodFilter
类。
3.1、OrderedHiddenHttpMethodFilter解析
该类继承自HiddenHttpMethodFilter
类,主要是绑定spring.mvc.hiddenmethod.filter
配置参数
而这里有个参数name = enabled
,并且默认是false
的也就是说默认并没有开启rest
映射。
3.2、HiddenHttpMethodFilter解析
HiddenHttpMethodFilter类首先是以过滤器的形式对请求进行过滤拦截,当请求过来被HiddenHttpMethodFilter拦截。
核心方法doFilterInternal解析如下:
-
首先这里必须是post请求,并且请求是合法的! 复制代码
-
然后获取到methodParam隐藏域的值,判断合法性 复制代码
-
不缺分请求方式值的大小写,并且全部转为大写。 复制代码
-
判断提交的请求是否是delete、put、PATCH请求。 复制代码
-
最后由HttpMethodRequestWrapper包装器解析并且返回,返回的值就是真正的请求方式! 复制代码
3.3、修改默认的_method参数【了解】
由于无法直接修改绑定的配置参数,进而无法将methodParam = _method
改成指定的名称。但是可以根据SpringBoot的条件装配进行装配。
可以看到这个类的装配只有IOC中没有HiddenHttpMethodFilter
类才会装配,那么我们可以通过set
方法手动向容器中注入这个Bean
,并且顺手指定methodParam
值。
由于手动向IOC中丢入HiddenHttpMethodFilter
类,那么条件装配将不会生效,因此这样就使用我们自定义的了,所以前面我们也说过凡是有@conditionalOnMissingBean
这类的注解,自定义配置就是大于默认的。
注意:这里前端传入的隐藏参数名称也要改为我们指定的query
进行转换才能使得rest生效。
3.4、总结
综述:Rest映射原理如下:
-
表单提交会带上/_method=PUT请求 复制代码
-
请求过来被HiddenHttpMethodFilter拦截 复制代码
-
查看请求是否正常,并且原生请求方式是POST 复制代码
-
获取到_method的值:比如DELETE,兼容以下请求;PUT.DELETE.PATCH【内部定义的允许的请求方式】 复制代码
-
原生request方式(post),包装模式requestWrapper重写了getMethod方法,返回的是传入的method值。 复制代码
-
过滤器链doFilter方法放行的时候用wrapper,以后的方法调用getMethod是调用requestWrapper的。 复制代码
3.5、Rest使用客户端工具
如PostMan可直接发送put、delete等方式请求,不需要进行上述的配置,无需过滤器,所以上面的所有的演示是选择性的对表单进行的开启配置。
4、SpringMVC请求处理-【源码分析】-请求映射原理
4.1、涉及的接口与类
每次发请求,Springboot是怎么找到哪个方法来处理我们的请求的呢,SpringBoot底层依然用了SpringMVC,因此我们先回顾一下SpringMVC当中,请求的原理涉及到的一些类和方法,如下:
- Servlet接口是最传统的接口,内部就四个方法,这里只列举一个核心方法service()。
- GenericServlet是一个抽象类,内部也没有给出具体的service()实现。
- HttpServlet这是有处理能力的Servlet实现类,实现了service()方法,并且针对不同的请求提供了不同的处理方法,这些请求统一交给了service调度。
- HttpServletBean主要是对HttpServlet进行了简单的扩展,其余的主要是后面两个子类进行强大的扩充。
- FrameworkServlet,首先重写了HttpServlet中的所有do请求,这些每一个do请求都会统一调度processRequest()方法;processRequest()方法再调度doService()方法进行处理;可惜的是doService并没有给出具体的实现。
- DispatcherServlet前端控制器,这里主要是实现doService方法,调度doDispath()方法进行分发给处理器映射器,并且串联上所有的拦截器组成一个执行链。最后再交给适配器…
SpringMVC的完整请求响应的流程图如下【参考SpringMVC】
因此SpringMVC功能分析从 org.springframework.web.servlet.DispatcherServlet -> doDispatch()开始【参考SrpingMVC流程】–>最终每个请求都会调用 doDispatch()方法
4.2、映射原理
虽然涉及的类、方法较多看起来十分的复杂,但是核心全都在FrameworkServlet和DispatcherServlet中,下面以一个基本的doGet请求进行debug分析。
4.2.1、FrameworkServlet解析
- FrameworkServlet收到请求后找到对应的执行doGet方法,之后doGet方法调processRequest进行一些参数和基本处理,然后交给抽象方法doService。
- 但是这里的doService没有办法完成处理,只能交给DispatcherServlet中的具体实现方法完成。
4.2.2、DispatcherServlet(前端控制器)
大致步骤如下:
- 首先是由FrameworkServlet执行doService方法进行处理的时候找到DispatcherServlet的具体实现方法doService进行完成。
- doService处理了一堆东西之后,找到doDispatch进行分发;此时的分发包括静态页面的请求与controller的请求,只要是请求就会走doDispatch方法。
- doDispatch进行工作,上来一顿处理操作然后判断是否是一个文件上传等一些判断之类的,开始进入正题,如何通过映射路径进行找到执行器…
4.2.3、映射处理器HandlerMapping
大家都叫它次级控制器,因为负责uri到controller的映射,通过映射处理器找到请求对应的控制器,在上述源码中体现为getHandler()方法,如下:
默认情况下什么都没有配置,但是自动装配了五个处理器映射器,如下,this.handlerMappings在Debug模式下展现的内容:
-
所有的HanlderMapping处理器映射器都保存了它应该管理的uri的映射!即保存了所有@RequestMapping 和handler的映射规则,
-
其中最熟悉的应该是RequestMappingHandlerMapping,保存了所有@RequestMapping注解的URI
WelcomePageHandlerMapping是处理欢迎页的处理器映射器。所以这样每个请求就找到了对应的控制器方法来进行请求的处理
4.2.4、HandlerAdapter适配器
适配器的功能很单纯,即针对不同的handler找到适配的处理器
根据这两步可以发现,基本的映射如何找到、如何适配的问题已经解决;最后会根据SpringMVC的流程图就是去执行对应的controller。
4.3、总结
大致步骤如下:
- 所有的请求映射规则都在HandlerMapping中:
- SpringBoot自动配置欢迎页的 WelcomePageHandlerMapping 。访问 /能访问到index.html;
- SpringBoot自动配置了默认的 RequestMappingHandlerMapping,请求进来,挨个尝试所有的HandlerMapping看是否有请求信息。
- 如果有就找到这个请求对应的handler,如上面流程进行接信息
- 如果没有就是找下一个 HandlerMapping,找完了依然没有找到对应的Handler那么就会出现异常,这些异常会使用异常处理机制(aop)集中处理,看到的就是一些错误页面…
- 因此如果我们需要一些自定义的映射处理,我们也可以自己给容器中放HandlerMapping。自定义 HandlerMapping
补充:IDEA快捷键:
- Ctrl + Alt + U : 以UML的类图展现类有哪些继承类,派生类以及实现哪些接口。
- Crtl + Alt + Shift + U : 同上,区别在于上条快捷键结果在新页展现,而本条快捷键结果在弹窗展现。
- Ctrl + H : 以树形方式展现类层次结构图。