Rest 映射及源码解析
# 190.Rest 映射及源码解析
SpringBoot 也支持 Rest 风格的 HTTP 请求
# 什么是 REST 风格的 HTTP 请求
SpringBoot 支持 Rest 风格(使用 HTTP 请求方式动词来表示对资源的操作)
以前的增删改查用户:请求的路径各不相同
- /getUser:获取用户
- /deleteUser:删除用户
- /editUser:修改用户
- /saveUser:保存用户
现在的增删改查用户: 只请求/user 路径,但是 HTTP 请求方式不同:
- GET:获取用户
- DELETE:删除用户
- PUT:修改用户
- POST:保存用户
之前我们学习 SpringMVC 的时候,讲过浏览器 form 表单只支持 GET 与 POST 请求,而 DELETE、PUT 等 method 并不支持,Spring3.0 添
加了一个过滤器,可以将浏览器请求改为指定的请求方式,发送给我们的控制器方法,使得支持 GET、POST、PUT 与 DELETE 请求。
其原理是用 HiddentHttpMethodFilter,用过滤器的方式来完成(参考 SpringMVC 常用注解 (opens new window))。具体怎么实现的呢?其要求前端传一个_method 参数,该参数就是真正的请求方式;然后 filter 读取这个值,然后将该 HTTP 请求转为该方式的请求,然后才是 handler 处理请求
# SpringBoot 中的 HttpMethodFilter
在 SpringBoot 中,其实已经配置了 1 个 HiddentHttpMethodFilter:可以看自动配置类 WebMvcAutoConfiguration,有一个 OrderedHiddenHttpMethodFilter 的组件:
public class WebMvcAutoConfiguration {
//.............
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter",
name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
//.............
2
3
4
5
6
7
8
9
10
# 开启配置
通过注解 ConditionalOnProperty,可以看到默认值 false。因此,我们配置下开启 REST 方式:修改 application.yml 文件,添加如下内容:
spring:
mvc:
hiddenmethod:
filter:
enabled: true
2
3
4
5
# 添加处理方法
我们来试试是否能用:
@RestController // @RestController = @Controller + @ResponseBody
public class HelloController {
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String getUser(){
return "GET-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String saveUser(){
return "POST-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.PUT)
public String putUser(){
return "PUT-张三";
}
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
public String deleteUser(){
return "DELETE-张三";
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PS,可以简写:
@GetMapping("/user")
public String getUser(){
return "GET-张三";
}
@PostMapping("/user")
public String saveUser(){
return "POST-张三";
}
@PutMapping("/user")
public String putUser(){
return "PUT-张三";
}
@DeleteMapping("/user")
public String deleteUser(){
return "DELETE-张三";
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 添加表单
在 index.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="DELETE">
<input value="REST-DELETE提交" type="submit">
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT">
<input value="REST-PUT提交" type="submit">
</form>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 测试
逐个点击按钮,可以看到能正常处理 REST 请求:
# 源码分析
我们根据源码,来再次说明一下原理(读者也可以加个断点,通过逐步调试的方式来观察执行过程)
首先,WebMvcAutoConfiguration
类中,会返回一个 Bean,叫做 OrderedHiddenHttpMethodFilter
:
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
2
3
4
5
6
而该类是继承了 HiddenHttpMethodFilter
:
public class OrderedHiddenHttpMethodFilter extends HiddenHttpMethodFilter implements OrderedFilter {
public static final int DEFAULT_ORDER = REQUEST_WRAPPER_FILTER_MAX_ORDER - 10000;
private int order = DEFAULT_ORDER;
@Override
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
在 HiddenHttpMethodFilter
类中,才是真正的处理请求的方法。我们看看其 doFilterInternal
方法:
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == 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);
}
}
}
filterChain.doFilter(requestToUse, response);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
该方法做了如下事情:
if 判断请求是否 POST 方式(这也就是为什么我们之前说,表单得是 POST 方式),并且请求得是正常的(没有 WebUtils.ERROR_EXCEPTION_ATTRIBUTE 这个属性的值,这个属性是错误信息)
然后获取到
_method
的值(第 8 行)然后判断
_method
不能是空的,并且统一转为大写(第 9,10 行,因此我们表单里可以忽略大小写)然后判断允许的请求方式中,是否有前端传过来的请求方式(第 11 行),该集合是这样定义的:也就是兼容 PUT,DELETE,PATCH 请求
private static final List<String> ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
1
2
3下一行(第 12 行)就是关键了:转换请求的方式。这是使用了一种设计模式:包装模式。首先,变量 requestToUse 是原生的 request 对象(第 5 行),然后 HttpMethodRequestWrapper,继承自 HttpServletRequestWrapper 类,而该类又继承自 HttpServletRequest,因此最后返回的也是原生的 Servlet 对象。
HttpMethodRequestWrapper 源码如下:
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
@Override
public String getMethod() {
return this.method;
}
}
2
3
4
5
6
7
8
9
10
11
12
可以看到,其修改了 method 属性(也就是 HTTP 请求方式),并重写了 getMethod 方法,返回的是传入的值。
最后,过滤器链放行的时候,用是包装过后的 requesWrapper 对象(第 17 行)。以后的方法调用 getMethod 方法时,是调用 requesWrapper 的。
# 直接发送 REST 请求
当然,上述处理方式主要是处理浏览器表单的,像一些 Rest 使用客户端工具,能直接发送 PUT、DELETE 请求的(例如安卓,PostMan),也就不会被过滤器给拦截。
这也就是为什么,如下配置是选择性开启的:
spring:
mvc:
hiddenmethod:
filter:
enabled: true
2
3
4
5
后续我们主要是作为微服务,只返回数据,不用直接处理表单,因此该项可能用的较少。
# 改变默认参数
之前我们要求是前端传参 _method
,接下来讲讲如何自定义这个名称,例如简单的改为 _m
。
首先,自动配置类中,是会判断是否有 HiddenHttpMethodFilter 这个类,没有的话才会注册该过滤器组件(第 4 行的注解,OnMissingBean)
public class WebMvcAutoConfiguration {
//.............
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter",
name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
//.............
2
3
4
5
6
7
8
9
10
而在过滤器的实现类 HiddenHttpMethodFilter
中,默认是 _method
属性(第 1 行)吗,并且是 final 类型的:
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = DEFAULT_METHOD_PARAM;
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
2
3
4
5
6
7
8
不过,后续定义了一个 set 方法,可以让我们修改。
综上,只要我们自己自定义了一个过滤器,然后调用 set 方法修改里面的值,就可以了。我们新建一个类:
package com.peterjxl.boot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter();
filter.setMethodParam("hiddenMethod");
return filter;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
注意:有两个 HiddenHttpMethodFilter
,第二个是响应式的,我们用第一个:
然后修改 index.html:
<form action="/user" method="post">
<input name="_method" type="hidden" value="DELETE">
<input name="_m" type="hidden" value="DELETE">
<input value="REST-DELETE提交" type="submit">
</form>
<form action="/user" method="post">
<input name="_method" type="hidden" value="PUT">
<input name="_m" type="hidden" value="PUT">
<input value="REST-PUT提交" type="submit">
</form>
2
3
4
5
6
7
8
9
10
11
重启,测试,效果是一样的。
# 源码
已将本文源码上传到 Gitee (opens new window) 或 GitHub (opens new window) 的分支 demo11,读者可以通过切换分支来查看本文的示例代码
- 01
- 中国网络防火长城简史 转载10-12
- 03
- 公告:博客近期 RSS 相关问题10-02