(一)请求映射@xxxMapping:REST风格支持(使用HTTP请求方式动词来表示对资源的操作)
同样进入WebMvcAutoConfiguration类,关注到hiddenHttpMethodFilter(我们的核心Filter):
①请求一传送进来会被hiddenHttpMethodFilter拦截到
注意:旧版本的源码中@ConditionalOnProperty注解中还有一个默认属性matchingIfMissing=false,就是如果springboot的yaml配置文件中没有该属性的值设置(spring.mvc.hiddenmethod.filter.enabled=true)的话,就不会匹配也就无法通过hiddenmethod实现delete或put。因此实现请求映射的方式是:表单method=“post”、隐藏域_method="post"。
因此在yaml中设置该值(在springboot中手动开启):
可以看到hiddenHttpMethodFilter方法返回的是OrderedHiddenHttpMethodFilter类型的:
进入HiddenHttpMethodFilter类,断点放在doFilterInternal方法首行:
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);
}
首句HttpServletRequest requestToUse = request;是请求本身。
②第一层if判断request.getMethod()的值是不是为POST,而且没有servlet的异常(请求是否正常),才可以继续。
获得this.methodParam的值,这里可以看到debug的值为_method。而且_method的值可以看到获得为paramValue变量,其值刚好是我们DELETE操作(也可以是PUT或PATCH),只是通过hiddenHttpMethodFilter的POST准过,实现ALLOWED_METHODS。
而且如果ALLOWED_METHODS中包含我们请求的方式(paramValue):
可见,ALLOWED_METHODS包含PUT、DELETE和PATCH。可以兼容他们这些方法。
原生的request是采用POST方式的,HttpMethodRequestWrapper继承了HttpServletRequestWrapper(原生的post),重写了getMethod()方法,从而将request的method从POST改为传入构造器的值(method)。
因此,进入前面第一个if时,request.getMethod()的这个getMethod是调用重写后的wrapper的,得到的也会是_method的值(DELETE、PUT、PATCH也有)。因此在controller注解中的method属性可以对应:
查看_method的值,应该为我们表单中设置的value的值:DELETE、PUT和PATCH。
附上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) {
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);
}
static {
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
public String getMethod() {
return this.method;
}
}
}
然后得到目标requestToUse对象:
注:即使value是‘delete’或者‘put’也会转为大写的_method的值。
这里指出如果隐藏域的name的值不为_method(默认值),是否可以修改?
注意到一开始的@ConditionalOnMissingBean的条件,如果有该filter类就不会启动(即按照自定义的类的规则来进行filtering。
因此这里自己来写一个这个filter,重写配置:
package com.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 methodFilter = new HiddenHttpMethodFilter();
// 根据前面分析过的逻辑,应该是可以修改的
methodFilter.setMethodParam("_m");
return methodFilter;
}
}
这里表单中的name值就可以改为我们修改后的_m了。
以上都是针对表单的(即原生的request的情况下使用filter解决),而遇到像REST使用客户端工具(如Postman),直接发送的是put、delete方式的请求,无需filter。
选择性(optional)开启页面表单的REST功能:
spring: mvc: hiddenmethod: filter: enabled: true
像以上的情况(GET)可以直接一个注释, 不需要method属性:
因为@GetMapping注解的底层也是@RequestMapping封装的:
同理对其他方式:
而且@xxxMapping与@RequestMapping的name属性一致:
(二)springboot中设置访问首页:
我试过的方式是:将首页(index.html)放在/resources/static静态资源文件夹下,然后在springboot的yaml配置文件中,不进行其他静态文件访问路径的配置。这样才可以访问到否则会一直转圈,无法进入首页访问。
还有一个未解之谜就是,在debug的时候我不知道是不是因为controller中一个方法的return处没有打断点,卡了很久找到一个办法:就是先正常run显示首页出来,然后debug再重新run的时候点击表单中的各个请求方式才能正常debug,否则获取不到_method的值。