• SpringMVC之RequestMapping执行过程(HandlerMapping下篇)


    写在前面

    在上篇中 回顾 here,我们已经介绍了 HandlerMethod 是如何被 自动扫描 出来,并注册到 RequestMappingHandlerMapping 中去的。

    本文将延续上一篇,探究当一个请求 HttpServletRequest 到来,SpringMVC 又是如何 匹配获取 到合适的 HandlerMethod 的?

    本文还是使用的上篇中的单元测试进行调试和分析的,有需要的可以到 Gitee here 上下载源码。

    getHandler 时序图

    在这张时序图中,最重要的一段方法就是 lookupHandlerMethod。接下来,对该方法逐段进行分析。

    1.getMappingByUrl

    getMappingByUrl 是在 lookupHandlerMethod 中调用的第一个方法。

    方法功能:根据 url 路径,获取可能与之匹配的 RequestMappingInfo 列表

    源码:

    urlLookup 的实例是 LinkedMultiValueMap

    LinkedMultiValueMapLinkedHashMap + LinkedList 的组合数据结构。

    LinkedMultiValueMap 添加的地方也仅有一处:

    源码

    在注册时,需要调用 getDirectUrlsReuqestMappingInfo 对象中提取作为索引 key 的 url 字符串。

    1.1.getDirectUrls

    源码

    getDirectUrls 里面有两个让人在意的点:

    1. 一个是 getPathMatcher() 返回一个什么样的对象?

    2. 另一个是 getMappingPathPatterns 得到了怎样的集合?

    1.2.AntPathMatcher

    首先回答第一个问题getPathMatcher() 返回一个什么样的对象?

    答案getPathMatcher() 默认返回的是 AntPathMatcher

    尽管可以自定义 PathMatcher 接口,但是 AntPathMatcher 还是比较常用的。Apache Ant 的官方指南 here

    AntPathMatcher 实现的是 Ant 风格的路径匹配(Ant-style path patterns),遵循的 URL 匹配规则如下:

    1. ? 匹配一个字符

    2. * 匹配 >= 0 个字符

    3. ** 匹配 >= 0 个目录

    4. 特别地,{url:[a-z]+} 表示路径变量 url 符合正则表达式 [a-z]+

    isPattern 源码:

    看到 isPattern 这段代码,我们就可以明白 getDirectUrls 希望获得 不含通配符 的 url,这也就是 direct url 的第一层含义。

    1.3.getMappingPathPatterns

    接着回答第二个问题getMappingPathPatterns 得到了怎样的集合?

    答案:返回 Controller 类上作用于类的 @RequestMapping 注解和 Controller 类中方法上的作用于方法的 @RequestMapping 注解的结合。

    追问1:为什么 RequestMapping 既可以是作用于类的注解,又可以是作用于方法的注解?

    奥秘就在于 RequestMapping 注解类上面 @Target 注解的作用目标的声明。

    追问2:为什么 getMappingPathPatterns() 能获取的是一个集合,而不是单个 url 呢?

    答案:因为 @RequestMapping 的 value (别名 path)是 String[]

    因此像这样的组合写法也是合法的。

    @Controller
    @RequestMapping({"/user", "/customer"})
    public class UserController {
    
        @RequestMapping({"/info","/detail"})
        public ModelAndView user(String name, int age) {
            System.out.println("name=" + name);
            System.out.println("age=" + age);
            return null;
        }
    }
    

    此时,可以匹配的 url 有 4 个:

    追问3:isPattern 没有排除 ${pathVariable} 的写法,那是不是表示 PathVariable 也算是 direct url 呢?

    答案: PathVariable 也算是 direct url。

    我们稍稍改变一下 UserController:

    @Controller
    @RequestMapping({"/user", "/customer"})
    public class UserController {
    
        @RequestMapping("${name}")
        public ModelAndView user(@PathVariable String name, int age) {
            System.out.println("name=" + name);
            System.out.println("age=" + age);
            return null;
        }
    }
    

    这种情况下,getDirectUrls 就会返回 ["/user/${name}", "/customer/${name}"] 字符串数组。

    2.addMatchingMappings

    我们知道 url 仅仅是一个索引,假如我们调用 getDirectUrls 能获取到结果,就意味着“命中索引”,效率就会高很多。

    否则就只能“全表搜索”了,效率就会低很多。

    来看一下源码:

    但是,无论是哪种方式,都会调用 addMatchingMappings。时序图如下:

    addMatchingMappings 方法中,foreach 遍历第一个参数——— RequestMappingInfo 集合 mappings

    • getMatchingMapping:如果 RequestMappingInfoHttpServletRequest 可以匹配,就返回一个新的 RequestMappingInfo对象,否则返回 null

    • getMatchingCondition:对 method,参数,请求头,consumes,produces,url patterns 等进行逐个匹配,假如有不匹配的,就返回 null。如果都能通过条件匹配,就返回一个新的 RequestMappingInfo对象。

    getMatchingCondition 匹配规则比较复杂,涉及的类也还有很多,就不在本文展开了。

    addMatchingMappings 方法中,每找到一个与 HttpServletRequest 匹配的 RequestMappingInfo,就会向参数 List<Match> matches 中加入一个 Match

    Match 是由 RequestMappingInfo 以及对应的 HandlerMethod 组成。

    3.对 Match 列表排序

    现在继续回到 lookupHandlerMethod 方法。

    既然,现在符合条件的 List<Match> matches 已经全部找出,我们就要排序并且筛选出最符合条件的 Match 了。

    其中,最佳匹配对象 Match,它的 HandlerMethod 就是最佳 HandlerMethod

    总结

    对于 RequestMappingHandlerMapping 而言,getHandler 就是要找一个最最匹配的 HandlerMethod 对象。

    这个寻找最佳匹配 HandlerMethod 的逻辑就“藏”在 lookupHandlerMethod 中,主要步骤如下:

    • 第一步,用请求的 url 路径获取 RequestMappingInfo 列表(“url索引匹配”);若匹配不上,只能全量遍历所有 RequestMappingInfo

    • 第二步,完全匹配 RequestMappingInfoHttpServletRequest。匹配逻辑在 RequestMappingInfo#getMatchingCondition 中。

    • 第三步,对匹配结果 Match 排序,选出最佳的 HandlerMethod,排序比较的逻辑在 RequestMappingInfo#compareTo 中。

  • 相关阅读:
    redisTemplate写哈希表遇到的坑
    embedded-redis在单元测试中的使用
    使用Standford coreNLP进行中文命名实体识别
    字符编码和文件编码
    Elasticsearch提示low disk watermark [85%] exceeded on [UTyrLH40Q9uIzHzX-yMFXg][Sonofelice][/Users/baidu/Documents/work/soft/data/nodes/0] free: 15.2gb[13.4%], replicas will not be assigned to this node
    nginx.conf常用配置解析
    使用nginx搭建文件下载服务器
    lua连接数据库操作示例代码
    spring常见注解说明
    lua相关库安装常见问题
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/springMvc-RequestMappingHandlerMapping-part2.html
Copyright © 2020-2023  润新知