0.引言
用过SpringBoot的想必都接触过@RestController这个非常常用的注解,今天我们就跟着restful风格的控制器来看一看spring处理请求参数的底层逻辑,实际上它的实现非常的巧妙,有助于我们深入的了解java这个语言。
1.请求映射举例
这里我们举一个非常简单的例子,来给后面的参数请求原理做铺垫,举例如下:
在controller的对应方法上加对应注解来声明我们这个方法是处理什么请求,这个过程就叫做请求映射
现在基本上都是用rest风格来处理,因此Mapping会有method属性(method不一样,url一样,结果也会不一样),几个重要的属性:
- value = “/path”,请求的地址
- method = RequestMethod.GET,也可以是POST,默认是get方式
- consumes:指定接收的处理请求的提交内容类型(Content-Type)
- produces:指定返回的内容类型,仅当request请求头中包含该指定类型才返回,比如要返回json,就用MediaType指定,或者produces = {"application/json;charset=UTF-8"},但是必须要和@ResponseBody注解一起使用才可以
- params:指定request中必须包含某些参数值,如请求必须带上指定参数(params = {"username=123"}),请求不可以带上某个参数params = {"!username"}等等
- headers: 指定请求头,request中必须包含某些指定的header值
注:如果需要用到delete和put方法就比较麻烦,需要在html界面做一些修改,但一般rest风格我们不用这两种方法,这里就不记录了
比如要从query中取一个参数作为入参,可以使用注解的方式,如下:
@RequestMapping("/test")
public String test0(@RequestParam("username") String name){
return name;
}
请求url为:http://localhost:8080/test?username=wuyu界面就会输出 wuyu,如果有多个值,比如 ?username=wuyu&username=dyc ,可以用List<String>来接收
@GetMapping("/test/{name}")
public String test0(@PathVariable("name") String name){
return name;
}
这样就可以用localhost:8080/test/wuyu来访问,当然也可以写多个@PathVariable来对应url中可能存在的多个值,当然如果有多个值我们一般就用map来接收,如下:
@GetMapping("/test/{name}/{id}")
public String test0(@PathVariable Map<String, String> total){
return total.get("name");
}
如果是POST请求,那输入就会有请求体,我们就需要用@RequestBody来接收,通常这样就需要用postman等工具来测试了,比如我们写一个如下的简单方法:
@PostMapping("/test")
public String test1(@RequestBody User user){
return user.toString();
}
User对象如下:
@Data
public class User {
private String name;
private Integer id;
private String context;
}
我们用postman发json类型的body请求,过去的json就对自动映射到User对象类的每一个属性中,然后toString出来,具体结果如下:
如果是要获取请求头,就用@RequestHeader,可以在浏览器里F12出来对应的,比如我们需要提取User-Agent这个参数就可以用@RequestHeader String userAgent来接收,如果只用@RequestHeader就需要用Map来接收所有的Header
如果需要获取cookie的值,就用@CookieValue,实际上cookies是在header里的,但如果我们想获取某一个cookies的值,就可以用Sting或Cookie类型来接收
2.什么是Rest风格
什么是rest风格相比大家应该都比较了解了,但这里还是简单的介绍一下,浓缩成一句话就是——使用HTTP请求的动词来表示对资源的操作
REST(Representational State Transfer)描述了一个架构样式的网络系统,比如 web 应用程序。在目前主流的三种Web服务交互方案中,REST相比于SOAP以及XML-RPC更加简单明了,无论是对URL的处理还是对Payload的编码,REST都倾向于用更加简单轻量的方法设计和实现。主要用于客户端和服务器交互类的软件。值得注意的是REST并没有一个明确的标准,而更像是一种设计的风格
Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可以由任何可用服务器回答,这十分适合云计算之类的环境
在服务器端,应用程序状态和功能可以分为各种资源。资源是一个有趣的概念实体,它向客户端公开。资源的例子有:应用程序对象、数据库记录、算法等等。所有资源都共享统一的接口,以便在客户端和服务器之间传输状态。使用的是标准的 HTTP 协议,每个资源都使用 URI得到一个唯一的地址,然后把原来的请求参数加入到请求资源地址中,改为使用HTTP协议中的请求方式GET、POST、PUT、DELETE(增,删,改,查)表示
3.Rest风格的实现逻辑
回到我们映射实现的底层逻辑上来,接触过Spring系列的读者都知道,我们一般在使用Rest风格的时候会用@PostMapping、@GetMapping,但实际上他们的底层就是我们最常用的@RequestMapping(method = "GET");虽然前者非常方便,不管是html的表单提交还是api暴露,都可以直接使用post、get、delete、put等方法。但是深入这些注解的底层,实际上这个功能的实现并没有那么容易
尤其是做web界面开发的表单提交,使用@RequestMapping的时候如果需要用到delete和put方法,是不太方便的,实际上他会自动转化成get方法。比如如下一个简单的表单提交,后面连接着的controller为@RequestMapping(method = "DELETE")这种模式,但访问的时候就会转换为默认值get,这是为什么呢?
<form action="/user" method="post">
<input value="Rest-test DELETE" type="submit">
</form>
实际上核心在于SpringBoot集成的SpringMVC中的一个Filter——HiddenHttpMethodFilter,、在SpringBoot的配置中,已经将OrderedHiddenHttpMethodFilter(继承于HiddenHttpMethodFilter)配置了一个默认的情况,如下图,我们进入web开发的配置类:WebMvcAutoConfiguration
接着进入OrderedHiddenHttpMethodFilter所继承的HiddenHttpMethodFilter,可以看到该过滤器下的这样一个属性——"_method",实际上就是这个属性设置了默认的请求方法,如果你不改动他那就是get
那我们怎么在html表单中开启除了get、post之外的请求方法呢?分如下几步:
1.表单提交的时候就设置默认方法,即“_method”参数;且以post方式提交
<form action="/user" method="post">
<input name="_method" type="hidden" value="delete">
<input value="Rest-test DELETE" type="submit">
</form>
2.表单提交的时候会发送一个请求,这个请求被OrderedHiddenHttpMethodFilter拦截住,首先要看这个filter是否开启,即:
从OrderedHiddenHttpMethodFilter的注解就可以看出来,我们必须要去配置里(application.yaml)来设置这个enable,并且在自己没有写其他配置的情况下,这个OrderedHiddenHttpMethodFilter才会启用
因此我们在yaml里加入如下代码来开启这个配置
spring:
mvc:
hiddenmethod:
filter:
enabled: true
3.进入doFilterInternal方法,核心就在于这个HttpMethodRequestWrapper,过滤器方向请求的时候,Wrapper将我们原生的post请求报装成了我们需要的请求,比如delete
4.通过这个方法,我们就可以得到通过表单提交的delete请求了,同理,put和patch也可以通过这种方法
如果是直接通过http访问,比如我们用postman请求,就不需要上面这些复杂的步骤了,因为这样我们是不通过filter的,这也是为什么OrderedHiddenHttpMethodFilter默认是不开启的,因为不是所有的开发者都需要表单提交这种模式
但是,做web开发,尤其是某些功能的可视化开发,表单提交往往是必须的,如果你使用的是@PostMapping、@GetMapping当然就不用担心这个问题(实际上也是系统帮你完成了这一个过程),但是一些比较老的项目会使用到上述的方式来实现其他几种请求方式;而且了解这一串的内部原理有利于我们深入理解rest风格在spring上的实现过程
4.手动设置Rest默认请求方式
刚刚讲到,由于OrderedHiddenHttpMethodFilter的@ConditionalOnMissingBean(FormContentFilter.class)这个配置,我们是完全可以自己做一个配置类的,自己设置默认参数的名字,比如我不想叫_method,也可以通过这种方式来调换,参考代码如下;
package com.example.springboot02.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
@Configuration
public class WebConfiguration {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_myname");
return methodFilter;
}
}
提交表单的时候把_method改成_myname即可