• SprignBoot中的一些小知识点(二)


    目录

    1、SpringBoot 集成SpringMVC 底层,表单提交REST风格请求,后端GET/POST/PUT/DELETE 四种方式处理细节

    2、请求映射原理(只看RequestMappingHandlerMapping)

    3、@MatrixVariable 接收请求的矩阵变量(不是传统意义的请求参数)


    1、SpringBoot 集成SpringMVC 底层,表单提交REST风格请求,后端GET/POST/PUT/DELETE 四种方式处理细节

    首先,前端表单只能发起 GET 和 POST 请求,但后端四种请求均可处理。那么前端如何发起DELETE 和 PUT 请求呢?

    如下图所示,四种请求,在后端不作任何配置的情况下,同样是 /user 路径,get 和 post 能找到对应的后端请求,但是 delete 和 put 却都跑到了 后端的 get 请求里面

    我们看一下SpringBoot 底层如何处理的请求

    我们找到 WebMvcAutoConfiguration  下的 Http方法过滤器

    (注释:SpringBoot 底层关于SpringMVC 的配置基本在 WebMvcAutoConfiguration 这个类中可以找到)

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

    我们发现,它已经默认给返回了一个过滤器,直接点进去 ,发现他继承了 HiddenHttpMethodFilter ,在 HiddenHttpMethodFilter 中我们发现真正过滤http方法的实现代码如下:

        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            HttpServletRequest requestToUse = request;
            // 当请求为POST 时
            if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
                // 续上,我才会将你请求中的一个key为 _method 的value拿出来
                String paramValue = request.getParameter(this.methodParam);
                // 如果传的方法名不为空
                if (StringUtils.hasLength(paramValue)) {
                    // 那么我帮你转成大写
                    String method = paramValue.toUpperCase(Locale.ENGLISH);
                    // 校验一下,是否在我的承受范围内,这里的 ALLOWED_METHODS ,见注释代码 2
                    if (ALLOWED_METHODS.contains(method)) {
                        // 最后返回一个包装的request(详见注释代码3),意思就是帮你转成你想要的那个 DELETE 或PUT
                        requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                    }
                }
            }
    
    
    
    
    注释代码:
    1、
        private String methodParam = "_method";
    
    2、    
    static {
            ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
        }
    
    3、 
    public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
                super(request);
                this.method = method;
            }
     HttpMethodRequestWrapper 继承了HttpServletRequest ,只是重写了他的构造方法,将你传进来的方法名称放进去而已

    那么,依照上面的规则,我们只需要在表单请求中添加key 为 _method 的参数,既可以让表单请求到想要的后端方法了

    修改过的表单代码如下:

    再次请求delete 和 put ,发现还是不对,反而两个都跳转 post 里面了

    先别着急,我们回过头再看下一开始 hiddenHttpMethodFilter 这个过滤器头上的注解

        //我是一个需要被IOC容器管理的组件
        @Bean
        //按需加载:如果用户没配我,我就加载我,反之不加载
        @ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
        //按需加载:看你有没有配置 spring.mvc.hiddenmethod.filter 这个属性,你配置true 我才加载它,你不配置,我就默认false,于是你后面的一切过滤条件都不会生效
        @ConditionalOnProperty(
            prefix = "spring.mvc.hiddenmethod.filter",
            name = {"enabled"},
            matchIfMissing = false
        )

    那好吧,我们在yaml中配置一下这个属性为true,再试

    发现,这次可以了✅

    2、请求映射原理(只看RequestMappingHandlerMapping)

    我们再深入一下,看SpringBoot底层处理请求,如何准确找到要访问的路径

    我们知道,所有请求来到后端都会经过DispacherServlet

    先找到DispacherServlet :/org/springframework/spring-webmvc/5.3.6/spring-webmvc-5.3.6.jar!/org/springframework/web/servlet/DispatcherServlet.class

    发现 doPost doGet 的实现,我们在 DispacherServlet 继承的 FrameworkServlet 中 找到了 doPost 的实现,而doPost又调用  processRequest()

        protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.processRequest(request, response);
        }

    继续进去processRequest  查看具体的实现代码,在DispacherServlet 中 找到了 doService 的实现代码

        protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
            this.logRequest(request);
            Map<String, Object> attributesSnapshot = null;
            if (WebUtils.isIncludeRequest(request)) {
                attributesSnapshot = new HashMap();
                Enumeration attrNames = request.getAttributeNames();
    
                label104:
                while(true) {
                    String attrName;
                    do {
                        if (!attrNames.hasMoreElements()) {
                            break label104;
                        }
    
                        attrName = (String)attrNames.nextElement();
                    } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));
    
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
    
            request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
            request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
            request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
            request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
            if (this.flashMapManager != null) {
                FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
                if (inputFlashMap != null) {
                    request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
                }
    
                request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
                request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
            }
    
            RequestPath previousRequestPath = null;
            if (this.parseRequestPath) {
                previousRequestPath = (RequestPath)request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
                ServletRequestPathUtils.parseAndCache(request);
            }
    
            try {
                // 前面都是初始化的set方法
                this.doDispatch(request, response);
            } finally {
                if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
                    this.restoreAttributesAfterInclude(request, attributesSnapshot);
                }
    
                ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
            }
    
        }
    

    掠过一系列set初始化方法,继续进入doDispatch()

        protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
            HttpServletRequest processedRequest = request;
            HandlerExecutionChain mappedHandler = null;
            boolean multipartRequestParsed = false;
            WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    
            try {
                try {
                    ModelAndView mv = null;
                    Object dispatchException = null;
    
                    try {
                        processedRequest = this.checkMultipart(request);
                        multipartRequestParsed = processedRequest != request;
                        // 这里最关键,获取处理器(即那个controller 处理请求)
                        mappedHandler = this.getHandler(processedRequest);
                        if (mappedHandler == null) {
                            this.noHandlerFound(processedRequest, response);
                            return;
                        }
    
                        HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                        String method = request.getMethod();
                        boolean isGet = "GET".equals(method);
                        if (isGet || "HEAD".equals(method)) {
                            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                            if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
                                return;
                            }
                        }
    
                        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                            return;
                        }
    
                        mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                        if (asyncManager.isConcurrentHandlingStarted()) {
                            return;
                        }
    
                        this.applyDefaultViewName(processedRequest, mv);
                        mappedHandler.applyPostHandle(processedRequest, response, mv);
                    } catch (Exception var20) {
                        dispatchException = var20;
                    } catch (Throwable var21) {
                        dispatchException = new NestedServletException("Handler dispatch failed", var21);
                    }
    
                    this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
                } catch (Exception var22) {
                    this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
                } catch (Throwable var23) {
                    this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
                }
    
            } finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    if (mappedHandler != null) {
                        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                    }
                } else if (multipartRequestParsed) {
                    this.cleanupMultipart(processedRequest);
                }
    
            }
        }
    

    只看关键的一部,进入 this.getHandler(processedRequest)

        @Nullable
        protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
            if (this.handlerMappings != null) {
                Iterator var2 = this.handlerMappings.iterator();
    
                while(var2.hasNext()) {
                    HandlerMapping mapping = (HandlerMapping)var2.next();
                    HandlerExecutionChain handler = mapping.getHandler(request);
                    if (handler != null) {
                        return handler;
                    }
                }
            }
    
            return null;
        }

    实际发起一个/ user 的 GET 请求,第一行断点,我们看到handlerMappings 中有四个mapping

    可以简单看到,第一个是关于controller 的mapping,第五个是欢迎页的mapping

    这个getHandler方法逻辑就是,拿request 通过while循环,匹配到适合的那个handler,并且返回给你供你使用

    3、@MatrixVariable 接收请求的矩阵变量(不是传统意义的请求参数)

    场景举例:通常会有一些信息存储在Session中,sessionId通常会存储在Cookie中进行前后端传递,如果用户吧该网站的Cookie 设置为禁用,那么我们就无法获取Cookie 进而无法获取Session中的某些信息。这时就可以使用区别于表单传参的方式,使用矩阵变量传参方式进行前后端交互。

    例如这样写

    前端:

    /car/sell;low=34;brand=byd.bmw

    后端接收:

    @GetMapping("cars/sell")
    public Map carSell(@MatrixVariable("low") Integer low,
                       @MatrixVariable("brand") List<String> brand){
              ...
    }

    但是矩阵变量在SpringBoot自动配置文件中的 UrlPathHelp 中又是默认禁止的,我们需要手动开启矩阵变量功能

    我们先看他是怎么禁用的,然后在去开启

    找到WebMvcAutoConfiguration 中的 configurePathMatch 路径映射配置

            public void configurePathMatch(PathMatchConfigurer configurer) {
                if (this.mvcProperties.getPathmatch().getMatchingStrategy() == MatchingStrategy.PATH_PATTERN_PARSER) {
                    configurer.setPatternParser(new PathPatternParser());
                }
    
                configurer.setUseSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseSuffixPattern());
                configurer.setUseRegisteredSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
                this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
                    String servletUrlMapping = dispatcherPath.getServletUrlMapping();
                    if (servletUrlMapping.equals("/") && this.singleDispatcherServlet()) {
                        UrlPathHelper urlPathHelper = new UrlPathHelper();
                        urlPathHelper.setAlwaysUseFullPath(true);
                        configurer.setUrlPathHelper(urlPathHelper);
                    }
    
                });
            }

    倒数第三行代码中的UrlPathHelper 中有一个 removeSemicolonContent 类型为布尔的变量默认是 true,他的意思就是移除分号内容

    这就说明但凡在一个后端采用SpringBoot框架构建的网站的健康的网址中加入分号的后缀,都会被默认过滤掉

    正好我们矩阵变量的传递方式也是以分号间隔的方式坠在网址后面,同样会被 UrlPathHelper 默认过滤掉

    解除默认过滤配置的封印需要自己新增配置类覆盖默认配置

    两种方式:第一种:配置类实现 WebMvcConfigurer 中的 configurePathMatch方法,并设置 removeSemicolonContent 为 false

                      第二种:自己新增一个配置类,并在配置类中新增一个用@bean注解加持的方法,返回一个 removeSemicolonContent 为 false 的WebMvcConfigrer

    两种方法如下图所示:

  • 相关阅读:
    西门子PLC 8种入门实例接线与控制
    安全继电器工作原理、接线图、使用方法图解
    RKNN--群聊天
    技术分享 | 无人机上仅使用CPU实时运行Yolov5?(OpenVINO帮你实现)(上篇)
    (博途)S7-300PLC传送带工件计数控制程序设计
    Adaptive Mixture Regression Network with Local Counting Map for Crowd Counting
    配色网站收集(持续更新...)
    VUE项目里个性化写个时间轴组件,带折叠效果
    纯CSS画尖角符号
    Docker部署
  • 原文地址:https://www.cnblogs.com/dk1024/p/14801725.html
Copyright © 2020-2023  润新知