SpringBoot请求映射源码分析(没看过源码的小白也能懂,比针尖还细)

一.前言

1.Rest风格的请求

我们现在一般喜欢用Rest风格的请求,即使用HTTP请求方式动词来表示对资源的操作。
举个例子:

  • 比如我们以前对学生信息进行相关的增删改查操作,定义的url路径可能是这样的:
    • /getStudent 获取学生信息
    • /deleteStudent 删除学生信息
    • /editStudent 修改学生信息
    • /savaStudent 保存学生信息
      如果这个项目比较大,我们的路径起名字都感觉很麻烦。
  • 现在我们用Rest风格这样做:
    • 所有对学生的操作都叫/Student,如何表示对学生的增删改查呢,使用HTTP请求方式动词来表示对资源的操作。
    • GET 方式请求表示获取学生信息
    • DELETE 方式请求表示删除学生信息
    • PUT 方式请求表示修改学生信息
    • POST 方式请求表示保存学生信息
      利用这些不同的请求方式动词来区分不同的请求。

2.表单如何发出delete和put请求

我们以前用SpringMVC来完成这些事情,我们需要配置一个叫HiddenHttpMethodFilter的Filter;
我们来到WebMvcAutoConfiguration中,可以看到它已经配置了一个HiddenHttpMethodFilter,如下:
在这里插入图片描述

也就是说默认我们的rest功能是可以用的,但是它通过@ConditionalOnProperty设置配置属性前缀spring.mvc.hiddenmethod.filter的配置属性名字enabled默认为false,来默认该功能是不开启的,我们在配置文件中手动开启它:

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true #选择性开启

为什么要配置HiddenHttpMethodFilter呢,因为我们的表单提交的请求方式中只支持GETPOST方式的请求,它不支持DELETEPUT 方式的请求,如下:

我们的表单的method只有两个选项,get和post:
在这里插入图片描述
那我们如何用表单发出delete和put请求呢? 我们可以这样做(为什么这样做我们之后在源码分析中会讲到),method还是等于post请求方式,但是添加了一个name="_method"value="DELETE"或者value="put"的input标签(value中的delete或者put大小写都可以,之后我们在讲解源码的过程中会看到SpringBoot会自动把其值全部变成大写),并且我们把这个input标签隐藏起来,如下:
在这里插入图片描述
这里先说一下为什么name="_method",这已经在HiddenHttpMethodFilter类中写好了,如下:
在这里插入图片描述
我们也可以调用setMethodParam设置它的值,如下:
在这里插入图片描述
自己新建一个配置类,用@Bean注入对象到容器中,在该方法返回对象前,调用setMethodParam来设置methodParam(也就是name)的值,如下:(这里只是做一个演示,下文的所有代码中没有该配置类)

在这里插入图片描述

3.完整代码示例:

Controller如下:

@RestController
public class HelloController {
    
    
    
//    @RequestMapping(value = "/student",method = RequestMethod.GET)
    @GetMapping("/student")
    public String getUser(){
    
    
        return "`GET` 方式请求表示获取学生信息";
    }

//    @RequestMapping(path = "/student",method = RequestMethod.POST)
    @PostMapping("/student")
    public String saveUser(){
    
    
        return "`POST` 方式请求表示保存学生信息";
    }

//    @RequestMapping(value = "/student",method = RequestMethod.PUT)
    @PutMapping("/student")
    public String putUser(){
    
    
        return "`PUT` 方式请求表示修改学生信息";
    }

//    @RequestMapping(value = "/student",method = RequestMethod.DELETE)
    @DeleteMapping("/student")
    public String deleteUser(){
    
    
        return "`DELETE` 方式请求表示删除学生信息";
    }
}

index.html:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<h1>SpringBoot Web ,欢迎您!</h1>
测试REST风格:
<!--method为空默认为get方式请求-->
<form action="/student" method="">
    <input value="GET请求" type="submit">
</form>
<form action="/student" method="post">
    <input value="POST请求"  type="submit">
</form>
<form action="/student" method="post">
    <input name="_method" value="DELETE" type="hidden">
    <input value="DELETE请求" type="submit">
</form>
<form action="/student" method="post">
    <input name="_method" value="put" type="hidden">
    <input value="PUT请求" type="submit">
</form>
<hr>
</body>
</html>

欢迎页面:
在这里插入图片描述

点击测试get请求:
在这里插入图片描述

点击测试post请求:
在这里插入图片描述

点击测试delete请求:
在这里插入图片描述

点击测试put请求:
在这里插入图片描述
可以看到这四种方式的请求我们都可以获取并访问到,那么底层源码是怎么写的呢,接下来我将具体讲解源码所做的一些事情。

二.源码分析

1.HiddenHttpMethodFilter类中的doFilterInternal方法

上述提到的hiddenHttpMethodFilter()方法中返回了OrderedHiddenHttpMethodFilter类的对象,把OrderedHiddenHttpMethodFilter对象注册到容器中:
在这里插入图片描述
而OrderedHiddenHttpMethodFilter类有继承了HiddenHttpMethodFilter类:
在这里插入图片描述
现在我们只需要来看HiddenHttpMethodFilter类了,而HiddenHttpMethodFilter类中的doFilterInternal方法是处理上述过程的主要方法体,我们给doFilterInternal方法打上断点,如下:
在这里插入图片描述

2.一步步分析源码:

启动debug来调试程序,接下来我们来一步步分析:
首先我们访问我们的欢迎页面:在这里插入图片描述
点击put请求,表单提交会带上name = value的信息, 在我们的put表单中,name="_method" value=“put”,如下:
在这里插入图片描述

所以表单提交时会带上_method=put的信息。

1.第一行代码

表单提交后请求会被HiddenHttpMethodFilter拦截,来到了doFilterInternal方法中,首先拿到我们的原生请求request,把它赋值给一个局部变量requestToUse,如下:
第一行代码:
在这里插入图片描述

2.第二行代码

然后进入if条件判断中,判断我们的原生request请求方式是不是POST方式(也就是我们form表单的method属性是不是post方式)和判断当前请求是否没有错误,如下:

刚才我们写的form表单:

在这里插入图片描述
第二行代码:
在这里插入图片描述
因为我们点击的是put,它的method属性是post,然后也没有什么错误,我们满足条件,进入if语句体中。

3.第三行代码

然后呢,接下来它调用request.getParameter方法获取请求参数并把它赋值给局部变量paramValue:
第三行代码:
在这里插入图片描述
其中request.getParameter实参的this.methodParam是什么呢?我们点进去看一下,如下:

在这里插入图片描述

它就是_method字符,这也就解释了为什么之前我们要在form表单中再添加一个input标签,其name值为_method,request.getParameter(_method)就或许到了其value值,因为我们刚才点击put请求,表单提交会带上name = value的信息, 在我们的put表单中,name="_method" value=“put”,所以局部变量paramValue得到的值为value的值,为put。

4.第四行代码

接着往下走,我们来到第四行代码,这里又是一个if语句判断,StringUtils.hasLength(paramValue)方法它判断的是字符串是否为null或者为为空,显然我们的paramValue不为null也不为空,为put,满足if判断条件。
StringUtils.hasLength方法:
在这里插入图片描述
第四行代码:
在这里插入图片描述

5.第五行代码

接着往下走,我们的第五行代码是把我们的paramValue局部变量变成大写,也就是把put变成大写的PUT,然后赋值给新的局部变量method,如下:
第五行代码:
在这里插入图片描述

6.第六行代码

又到了if语句判断的地方,这里判断我们的局部变量method(也就是PUT)是否包含在ALLOWED_METHODS这个List集合中,为什么ALLOWED_METHODS是一个集合呢?我们点进去看一下就知道了:
在这里插入图片描述
它是这么一个集合,它里面有PUT、DELETE、和PATCH,它们都是枚举类型,我们分别点进去看一下:
在这里插入图片描述
它们的name方法是得到其名称,也就是同名的字符串,也就得到了PUT,DELETE和PATCH字符串。
我们的局部变量method(也就是PUT)在其中,if语句为true。
第六行代码:
在这里插入图片描述

7.第七行代码

继续往下走,接下来遇到了一个new HttpMethodRequestWrapper新建了一个对象来取代原来的原生request,我们点进这个HttpMethodRequestWrapper中看一下:
在这里插入图片描述
我们发现这个HttpMethodRequestWrapper继承了HttpServletRequestWrapper,我们点进HttpServletRequestWrapper看一下:
在这里插入图片描述
发现这个HttpServletRequestWrapper最终还是实现了HttpServletRequest,所以呢HttpServletRequestWrapper的子类HttpMethodRequestWrapper他还是一个HttpServletRequest请求。
现在我们回到HttpMethodRequestWrapper类中,具体看其类内部是怎么回事:
我们调用了HttpMethodRequestWrapper的构造器,传递了两个参数分别是原生的request请求和method(也就是PUT)请求方式,在构造器中我们把请求方式method赋值给类的属性method,如下:
在这里插入图片描述
然后重写了父类的getMethod方法,返回我们刚刚赋值的method(也就是PUT )。
调用了构造器之后把其创建的对象赋值给了局部变量requestToUse,当我们调用requestToUse的getMethod方法时,我们返回的就是PUT了,这个地方使用了一个包装设计模式,很巧妙。
第七行代码:
在这里插入图片描述

8.第八行代码

调用filter过滤器filterchain的dofilter方法,将把自身接收到的请求request对象和response对象和自身对象即filterchain作为下一个过滤器的dofilter的形参传递过去,这样才能使得过滤器传递下去。但是这里的request对象是requestToUse,也就是我们刚刚重写了getMethod方法的requestToUse,当调用getMethod的方法时返回的是PUT。
第八行代码:
在这里插入图片描述

dubug放行后运行结果:
在这里插入图片描述
上述我们都说的是表单提交的方式发送请求。
如果是客户端直接发送请求,我们的请求方式可以直接是PUT、DELETE等方式。如果不是POST请求它会只运行第一行代码和第八行代码。

猜你喜欢

转载自blog.csdn.net/MrYushiwen/article/details/112665170
今日推荐