(一)请求映射@xxxMapping:REST风格支持(使用HTTP请求方式动词来表示对资源的操作)

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_spring boot

同样进入WebMvcAutoConfiguration类,关注到hiddenHttpMethodFilter(我们的核心Filter): 

①请求一传送进来会被hiddenHttpMethodFilter拦截到

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_表单_02

注意:旧版本的源码中@ConditionalOnProperty注解中还有一个默认属性matchingIfMissing=false,就是如果springboot的yaml配置文件中没有该属性的值设置(spring.mvc.hiddenmethod.filter.enabled=true)的话,就不会匹配也就无法通过hiddenmethod实现delete或put。因此实现请求映射的方式是:表单method=“post”、隐藏域_method="post"

因此在yaml中设置该值(在springboot中手动开启):

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_REST_03

可以看到hiddenHttpMethodFilter方法返回的是OrderedHiddenHttpMethodFilter类型的:

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_表单_04

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_表单_05

进入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的异常(请求是否正常),才可以继续。

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_java_06

获得this.methodParam的值,这里可以看到debug的值为_method。而且_method的值可以看到获得为paramValue变量,其值刚好是我们DELETE操作(也可以是PUT或PATCH),只是通过hiddenHttpMethodFilter的POST准过,实现ALLOWED_METHODS。

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_spring_07

而且如果ALLOWED_METHODS中包含我们请求的方式(paramValue):

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_spring boot_08

可见,ALLOWED_METHODS包含PUT、DELETE和PATCH。可以兼容他们这些方法。

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_REST_09

        原生的request是采用POST方式的,HttpMethodRequestWrapper继承了HttpServletRequestWrapper(原生的post),重写了getMethod()方法,从而将request的method从POST改为传入构造器的值(method)。

        因此,进入前面第一个if时,request.getMethod()的这个getMethod是调用重写后的wrapper的,得到的也会是_method的值(DELETE、PUT、PATCH也有)。因此在controller注解中的method属性可以对应:

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_spring boot_10

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_spring_11

查看_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对象: 

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_java_12

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_java_13

注:即使value是‘delete’或者‘put’也会转为大写的_method的值。

这里指出如果隐藏域的name的值不为_method(默认值),是否可以修改?

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_java_14

注意到一开始的@ConditionalOnMissingBean的条件,如果有该filter类就不会启动(即按照自定义的类的规则来进行filtering。

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_表单_02

因此这里自己来写一个这个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属性:

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_spring boot_16

因为@GetMapping注解的底层也是@RequestMapping封装的:

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_spring boot_17

同理对其他方式:

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_spring_18

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_表单_19

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_表单_20

而且@xxxMapping与@RequestMapping的name属性一致:

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_REST_21

(二)springboot中设置访问首页:

我试过的方式是:将首页(index.html)放在/resources/static静态资源文件夹下,然后在springboot的yaml配置文件中,不进行其他静态文件访问路径的配置。这样才可以访问到否则会一直转圈,无法进入首页访问。

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_spring boot_22

 

springboot怎么禁止已经实现的CommandLineRunner springboot禁用put_REST_23

还有一个未解之谜就是,在debug的时候我不知道是不是因为controller中一个方法的return处没有打断点,卡了很久找到一个办法:就是先正常run显示首页出来,然后debug再重新run的时候点击表单中的各个请求方式才能正常debug,否则获取不到_method的值。