概述
请求参数处理
@xxxMapping
Rest 风格:以HTTP方式动词来表示对资源的操作
以前 /getUser /deleteUser分开写
现在 /user Get-获取用户 Delete-删除用户 Put-修改用户 Post-保存用户
核心Filter HiddenHttpMethodFilter
用法 表单method=post 隐藏域 _method=put
验证:表单只有get/post两种method,没有其他的,其他默认都是get
<form action="/user" method="get">
<input value="Rest-Get" type="submit">
</form>
<form action="/user" method="post">
<input value="Rest-Post" type="submit">
</form>
<form action="/user" method="put">
<input value="Rest-Put" type="submit">
</form>
<form action="/user" method="delete">
<input value="Rest-Delete" type="submit">
</form>
如何处理put和delete,查看
系统中已经有WebMvcAutoConfiguration
其下有
向上
再向上找到 "_method",即需要带一个隐藏的method过来
该类也规定了只支持POST
据此修改html表单
<form action="/user" method="get">
<input value="Rest-Get" type="submit">
</form>
<form action="/user" method="post">
<input value="Rest-Post" type="submit">
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT">
<input value="Rest-Put" type="submit">
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="DELETE">
<input value="Rest-Delete" type="submit">
</form>
此时并没有生效,回去查看OrderedHiddenHttpMethodFilter
重读 spring.mvc.hiddenmethod.filte要求,需要配置spring.mvc.hiddenmethod.filter
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(
prefix = "spring.mvc.hiddenmethod.filter",
name = {"enabled"}
)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
更改yaml
spring:
# mvc:
# static-path-pattern: "/res/**"
web:
resources:
# add-mappings: false
## static-locations: classpath:/allimage
# static-locations: [classpath:/allimage]
cache:
period: 11000
mvc:
hidden-method:
filter:
enabled: true
Rest拦截原理
public class HiddenHttpMethodFilter extends OncePerRequestFilter
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
1)表单请求会带_method_PUT参数,请求过来,被 我们开启的HiddenHttpMethodFilter拦截
首先拿到这个请求,变成requestToUse,也是原生请求
如下位置设立断点
Debug运行,回到html点击Rest-Put
就会进入断点
接下来判断是不是POST,并且当前请求没有什么错误
2)拿到_method值并使用Wrapper包装
Stepover下一行
寻找this.methodParam,发现了
methodParam = "_method";
Stepover下一行发现:如下灰色部分,已经获取到paramValue="PUT",即不是空的
接下来大小写转化:全部转成大写,所以其实表单上大小写都行,注意method在此被赋值为"PUT"
String method = paramValue.toUpperCase(Locale.ENGLISH);
Stepover下一行,用于判断允许的请求方式包不包含 PUT
if (ALLOWED_METHODS.contains(method)) {
什么是ALLOWED_METHODS在本类中可以找到
PUT DELETE PATCH
因为在其范围内Stepover下一行,重点是Wrapper
requestToUse = new HttpMethodRequestWrapper(request, method);
进入HttpMethodRequestWrapper的父类,发现其还是implements HttpServletRequest,即实现了原生Request请求
但是Wrapper接收了一个传参method
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
Wraper重写了return (HttpServletRequest)super.getRequest();
总结:
1)原生是Request(post)
2)包装模式HttpServletRequestWrapper也是继承了HttpServletRequest,重写了getMethod();
返回的是传入的_mothod传入的值,
3)过滤链返回及放行
返回给requestToUse
放行Filter :filterChain.doFilter((ServletRequest)requestToUse, response);
放行Wrapper对象
这样contoller里面RequestMethod调用的是Wrapper对象_method
4)备注
只有浏览器的请求涉及这个过滤
其他来自比如android或者postman直接就可以发出 PUT DeLETE,不经过过滤器
因为没有_method
Controller注解进化
@GetMapping @PostMapping @PutMapping @DeleteMapping
@RestController
public class HelloController {
@RequestMapping("/1.png")
public String hello(){
return "静态请求";
}
//@RequestMapping(value="/user",method = RequestMethod.GET)
@GetMapping("/user")
public String getUser(){
return "Get-李白";
}
// @RequestMapping(value="/user",method = RequestMethod.POST)
@PostMapping("/user")
public String saveUser(){
return "Save-李白";
}
// @RequestMapping(value="/user",method = RequestMethod.PUT)
@PutMapping("/user")
public String putUser(){
return "Put-李白";
}
// @RequestMapping(value="/user",method = RequestMethod.DELETE)
@DeleteMapping("/user")
public String deleteUser(){
return "Delete-李白";
}
}
自己写新的,不用_Method
使用_method,关键是OrderedHiddenHttpMethodFilter,而它有个前提条件
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
就是先判断有没有HiddenHttpMethodFilter.class,这样我们就可以自己写
另外在HiddenHttpMethodFilter中有个setMethodParam方法
这样写出了一个新的配置类
package com.i7i8i9.boot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
@Configuration(proxyBeanMethods = false) //false代表组件之间没有依赖,可以快速编译
public class WebConfig {
@Bean
public HiddenHttpMethodFilter HiddenHttpMethodFilter(){
HiddenHttpMethodFilter hiddenHttpMethodFilter=new HiddenHttpMethodFilter();
hiddenHttpMethodFilter.setMethodParam("_mm");
return hiddenHttpMethodFilter;
}
}
继续打断点来看
额外:鼠标放在下文request处一会,出现并点加号,可以看到request包含了什么
确认一下请求方式是不是POST,这个语句右键点 Evaluate
可以看到this.methodParam被改成_mm,因为html还没改自然后面获取不到,只能按照原生的放行,也就是GET,DELETE都处理成POST
<---------------------------------结束------------------------------------->
请求映射原理
1)请求如何找到
因为底层是Spring MVC, 所有的请求都会到DispatcherServlet
DispatcherServlet最终继承于HttpServlet,也必然要重写doGet doPost方法
可以发现在FrameworkServlet重写了doGet DoPost
最终会调用到
processRequest(request, response);
里面有doService
try { this.doService(request, response);
点击进入发现是一个抽象方法
那么在其实现类DispatcherServlet 中继续找
总结
HttpServlet -----请求进来使用了 DoGet方法
-- HttpServletBean
--FrameWorkServlet 重写doGet,调用processRequest-->doService
--DispatcherServlet 实现了doService-->doDispatch
doDispatch就是每个请求都会调用的方法
SpringMVC功能都从org.springframework.web.servlet.DispatcherServlet#doDispatch开始
doService的核心doDispatch
进入doDispatch
2)doDispatch研究
断点
去除其它断点 ,Debug模式下点击
去除其它断点
回到首页发送请求,后退回首页
从收到的request可以看到当前请示是 /
放行该请求
页面可以出现了
再在页面上点击
可以看到Request变成/user,也就是发送了一个user请求
一路stepover到
processedRequest = this.checkMultipart(request);检查是否文件上传请求
继续step over到神奇之处,在此找到哪个handler也就是controller的方法来处理
mappedHandler = this.getHandler(processedRequest);
重新设置断点
点击html页面元素,后端processedRequest获取参数如下:
点击stepinto看到了 handlerMappings
--------------------------------------------------------------------------------------------------------------------------
handlerMappings
默认有5个
1) welcomePageHandlerMapping用于处理欢迎页
pathMatcher中/ 被映射到
映射到view:index.html
2)RequestMappingHandlerMapping
这个是针Controller中 @RequestMapping注解的映射,保存了所有 @RequestMapping和Handler的映射规则
spring启动时自动扫描获得的规则
3)轮询查找哪个handlermapping可以处理
继续放行到
HandlerExecutionChain handler = mapping.getHandler(request);
在上一行mapping 上展开
mappingRegistry(可以右键copy name)
打开之后就看到了注册中心,当前项目写的都在里面
继续Stepinto
继续Stepinto
继续StepOver,就到了return var2 -
stepinto return var2终于找到了
String lookupPath = this.initLookupPath(request); 拿到原始请求
step over可以看到 lookupPath已经获得了要访问的路径 /user
this.mappingRegistry.acquireReadLock();表示拿到了一把锁,避免并发查询mappingRegistry
因为mappingRegistry保存了所有请求映射关系
继续stepover
HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);
寻找lookupPath代表的路径请求谁来处理
Stepinto
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) 带入参数要寻找的路径,和原始请求
接下来stepover到 开始从mappingRegistry寻找
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
但是/User下包含了 四种method put get post delete
先找lookpath=/user
stepover 发现directPathMatches找到了4个
继续stepover,把所有找到的添加入集合matches
到此,matches找到了最佳匹配
《---------------------------------------------------------------》
其他说明,如果有多个处理同一请求user/post的那么会默认使用第一个并且可能抛出异常
stepover到这里,也就是matches不是空
AbstractHandlerMethodMapping<T>.Match bestMatch = (Match)matches.get(0)
bestmathces也找到了
如果同时找到了好多,就认为第一个是最匹配的, 先拿到第一个
注意断点在当前行表示还没有执行,所以是Cannot find local variable
if (matches.size() > 1) { // matches.size() > 1表示同时找到了非常多
再来分析一个index页面请求
重设断点org.springframework.web.servlet.HandlerExecutionChain
DispathcerServlet.class
1)刷新index页面,到达断点 ,可以看到当前请求确实是访问“/”
2)stepinto +stepover,到达 handler = this.getDefaultHandler();
如下 this中mappingrestry只包含了自己写的controller,没找到
完成第一次循环,回到循环处,再stepover进行第二次循环
这个时候看mapping已经到了第二个循环,到了Welcome这里
多次stepover回到断点,stepinto
这个时候发现handler已经找到了
其处理的路径恰好是 / 也就找到了
spring Boot为我们配置了哪些handlermapping
查找WebMvcAutoConfiguration
1)找到第一个RequestMappingHandlerMapping 以@Bean加入到容器,给所有标注了@mapping 注解的controller
2) WelcomePageHandlerMapping 都是以Bean的方式注册到容器中
3)如果我们需要自定义handler mapping,也可以以Bean的方式加入到容器
比如未来可能有 /api/v1/user /api/v2/user
需要配置如果是 /api/v1去哪个包里找, /api/v2去哪个包里找,
最后
以上就是小巧眼神为你收集整理的SpringBoot2-7 Web2 请求参数处理 只有Post才能带隐藏_method,只允许额外三种(PUT DELETE PATCH);doDispatch就是每个请求都会调用的方法请求参数处理Controller注解进化 自己写新的,不用_Method请求映射原理handlerMappings 默认有5个 spring Boot为我们配置了哪些handlermapping的全部内容,希望文章能够帮你解决SpringBoot2-7 Web2 请求参数处理 只有Post才能带隐藏_method,只允许额外三种(PUT DELETE PATCH);doDispatch就是每个请求都会调用的方法请求参数处理Controller注解进化 自己写新的,不用_Method请求映射原理handlerMappings 默认有5个 spring Boot为我们配置了哪些handlermapping所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复