zoukankan      html  css  js  c++  java
  • Spring Boot核心技术之Restful映射以及源码的分析

    Spring Boot核心技术之Rest映射以及源码的分析

    该博客主要是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站尚硅谷

    结束:

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

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

  • 相关阅读:
    Linux问题分析或解决_samba无法连接
    Android系统编译环境及连接工具配置
    Linux问题分析或解决_ssh无法连接
    新能力| 云开发静态网站托管能力正式上线
    如何在云开发静态托管中使用Hugo
    如何在云开发静态托管绑定静态域名
    3步搞定图像盲水印?试试云开发扩展能力
    不再忍受龟速 Github,你也可以试试在云开发上部署个人博客!
    云开发 For Web:一站式开发下一代 Serverless Web 应用
    下笔如有神的程序员来教你写作啦!
  • 原文地址:https://www.cnblogs.com/xbhog/p/15102430.html
Copyright © 2011-2022 走看看