SpringBoot2-7 Web2 请求参数处理 只有Post才能带隐藏_method,只允许额外三种(PUT DELETE PATCH);doDispatch就是每个请求都会调用的方法

请求参数处理

@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去哪个包里找,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值