【springboot】请求处理GET/POST/DELETE/PUT的源码分析暨springboot中设置访问首页

(一)请求映射@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的值。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值