阅读 251

SpringBoot核心技术之Restful映射以及源码的分析

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

该博客主要是Rest映射以及源码的分析,主要是思路的学习。SpringBoot版本:2.4.9

环境的搭建

主要分两部分:

image-20210801160602153

Index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
  <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>

</body>
</html>
复制代码

controller

package com.xbhong.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@RestController
public class myContro {
    @RequestMapping(value = "/user",method = RequestMethod.GET)
    public String getUser(){
        return "GET-张三";
    }

//    @PostMapping("/user")
    @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-张三";
    }
}
复制代码

测试功能是否可用:

观察效果:

Rest映射1

可以看到最后两个请求都变成Get的请求了。由此我们可以引出来如下问题:

  1. 为什么不同的请求会出现相同的结果
  2. 怎么实现的才能完成我们的功能

后面的文章就是围绕上述的问题进行展开的。

解决问题:

像之前的SpringMVC中的表单请求通过HiddenHttpMethodFilter实现的,这样我们查一下在SpringBoot中是怎么样的。

默认双击Shift键,输入WebMvcAutoConfiguration这个类

找到默认配置的过滤器:

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter();
}
复制代码

通过OrderedHiddenHttpMethodFilter进入父类HiddenHttpMethodFilter:

在使用之前,我们需要将后两个的请求方式改写成post方式,并且需要在请求的时候传入一个_method方法(设置隐藏域);

<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>
复制代码

为什么要这样设置呢?我们到HiddenHttpMethodFilter中看下。

image-20210801165914667

流程:

  1. 第一步保存了传入的请求
  2. 当该请求时post,并且请求没有异常,才能进入下面方法,不是Post请求将直接通过过滤器链放行。
  3. 获取请求中的参数(this.methodParam)
  4. DEFAULT_METHOD_PARAM = _method获得(作为真正的请求方式)

然后再测试,发现还是没有实现。

我再回到WebMvcConfiguration中:

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter();
}
复制代码

首先该类存在于容器中。

@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
复制代码

当容器中不存在HiddenHttpMethodFilter这个类的时候,下面内容开启(条件装配);

@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
复制代码

表示:绑定的配置文件中:spring.mvc.hiddenmethod.filter名字为enable是默认不开启的(后续版本可能开启)。这样我们就找到了问题的所在。

所以我们需要在配置文件中配置(两种方法都可以)。

yaml:

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true
复制代码

properties:

spring.mvc.hiddenmethod.filter.enabled=true
复制代码

重启项目:成功解决。

Rest映射2

源码分析:

主要是分析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);
}
复制代码
  • 表单提交会带上**_method=PUT**

  • 请求过来被HiddenHttpMethodFilter拦截

  • 请求是否正常,并且是POST

  • 获取到**_method**的值。

  • 兼容以下请求;PUT.DELETE.PATCH

    当方法走到上述代码11行时,进入ALLOWED_METHODS:

    private static final List<String> ALLOWED_METHODS =
    			Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
    					HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
    复制代码

    如果请求里的参数在ALLOWED_METHODS存在,则执行下面代码。

  • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。

    进入HttpMethodRequestWrapper对象中,向上找父类。本质还是HttpServletRequest

    由下面代码:

    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;
        }
    }
    复制代码

    可知,接收到前面的请求封装为HttpMethodRequestWrapper返回。

  • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的。

部分补充:

由于源码中规则:将获得请求中的参数无条件的以英文格式转完成大写,所以前端的value="PUT"value的值大小写无影响。

String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
    String method = paramValue.toUpperCase(Locale.ENGLISH);
复制代码

Rest使用客户端工具,

  • 如PostMan直接发送Put、delete等方式请求,无需Filter。

参考文献:

B站尚硅谷

结束:

如果你看到这里或者正好对你有所帮助,希望能点个关注或者推荐,感谢;

有错误的地方,欢迎在评论指出,作者看到会进行修改。

文章分类
后端