1、SpringBoot和REST风格
当一个请求过来,SpringBoot最先要看的就是----谁能处理这个请求?交由SpringMVC去逐步解析。Spring 4.3后,添加GetMapping/PostMapping/PutMapping/PatchMapping/DeleteMapping注解,以支持REST风格。REST风格被推荐为各个微服务系统之间用于交互的方式 。此风格中,每一个资源都只是对应着一个网址,而一个代表资源网址应该是一个名词,而不存在动词,而简易参数尽量通过网址进行传递。
来看一下Rest风格下对User操作和以前有什么不同:
- 以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
- 现在: /user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
2、测试
简单测试一下:
@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-张三";
}
从idea的HTML智能提示可以看到,对于表单只有两种提交方式:get、post,测试以下代码,
<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="delete">
<input value="REST-DELETE 提交" type="submit">
<form>
<form action="/user" method="put">
<input value="REST-PUT 提交" type="submit">
<form>
在method里面写put和delete,最终都转向get的请求处理,但是要使用REST风格,怎么办呢?我们已知Spring4.3是支持REST风格的。以前的做法是给SpringMVC配置HiddenHttpMethodFilter。所以查看一下SpringBoot对于SpringMVC的支持所在的类:WebMvcAutoConfiguration
可以看到默认是配置了HiddenHttpMethodFilter,点开方法返回的new OrderedHiddenHttpMethodFilter()
,再点开这个类继承的extends HiddenHttpMethodFilter
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
private static final List<String> ALLOWED_METHODS;
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = "_method";
public HiddenHttpMethodFilter() {
}
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
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) {
...
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
...
}
要使用Rest风格,只需要带一个隐藏的参数项_method
即可,对Html改动一下。从上面的源码可以看出:只有当你的提交方式是POST,才拿到_method
参数的值,进行包装。
<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>
再次测试发现,delete和put还是跳转到默认的get请求处理,原因在于:在WebMvcAutoConfiguration中确实配置了Bean,但是它默认是关闭(false)的。
@Bean
//当容器中没有这个bean时才会帮我们配置
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
//是否配置了这个属性,没配就默认false
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
在application.yaml文件中开启功能
spring:
mvc:
hiddenmethod:
filter:
enabled: true #开启页面表单的Rest功能
再次测试,成功了,delete和put都找到了自己的请求处理。
3、原理解析(表单)
Rest原理(表单提交要使用REST的时候)
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 HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
• 表单提交会带上_method=PUT
• 请求过来被默认配置的HiddenHttpMethodFilter拦截
• 判断请求是否正常,并且是POST
-
获取到_method的值。
-
是否是兼容的以下请求;PUT.DELETE.PATCH
-
原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的
_method
的值。 -
过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。
4、@RequestMapping派生注解
@RequestMapping(value = "/user",method = RequestMethod.GET)
等同于GetMapping("/user")
。
依此类推还有:PostMapping("/user")
,DeleteMapping("/user")
,PutMapping("/user")
5、扩展:自定义HiddenHttpMethodFilter
在配置类中加入:
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
通过原类中提供的setMethodParam方法更改_method
为_m
,这样在HTML中写的参数名就得使用_m
,否则delete、put将会转到默认的get请求处理,因为原先的_method
已无效。
2020/12/24,学习b站尚硅谷SpringBoot2整理的笔记,继续加油!