我们已经介绍了 HandlerMethod 是如何被 自动扫描 出来,并注册到 RequestMappingHandlerMapping 中去的。
本文将延续上一篇,探究当一个请求 HttpServletRequest 到来,SpringMVC 又是如何 匹配获取 到合适的 HandlerMethod 的?
getHandler 时序图
![](https://img.haomeiwen.com/i26273155/ff07ef45c985c6de.png)
在这张时序图中,最重要的一段方法就是 lookupHandlerMethod。接下来,对该方法逐段进行分析。
1.getMappingByUrl
getMappingByUrl 是在 lookupHandlerMethod 中调用的第一个方法。
方法功能:根据 url 路径,获取可能与之匹配的 RequestMappingInfo 列表
源码:
image.png
urlLookup 的实例是 LinkedMultiValueMap:
image.png
LinkedMultiValueMap 是 LinkedHashMap + LinkedList 的组合数据结构。
向 LinkedMultiValueMap 添加的地方也仅有一处:
源码:
image.png
在注册时,需要调用 getDirectUrls 从 ReuqestMappingInfo 对象中提取作为索引 key 的 url 字符串。
1.1.getDirectUrls
源码:
image.png
getDirectUrls 里面有两个让人在意的点:
-
一个是 getPathMatcher() 返回一个什么样的对象?
-
另一个是 getMappingPathPatterns 得到了怎样的集合?
1.2.AntPathMatcher
首先回答第一个问题:getPathMatcher() 返回一个什么样的对象?
答案:getPathMatcher() 默认返回的是 AntPathMatcher。
尽管可以自定义 PathMatcher 接口,但是 AntPathMatcher 还是比较常用的。Apache Ant 的官方指南 here
AntPathMatcher 实现的是 Ant 风格的路径匹配(Ant-style path patterns),遵循的 URL 匹配规则如下:
-
?
匹配一个字符 -
*
匹配 >= 0 个字符 -
**
匹配 >= 0 个目录 -
特别地,
{url:[a-z]+}
表示路径变量 url 符合正则表达式[a-z]+
isPattern 源码:
image.png
看到 isPattern 这段代码,我们就可以明白 getDirectUrls 希望获得 <mark style="margin: 0px; padding: 0px; box-sizing: border-box;">不含通配符 的 url</mark>,这也就是 direct url 的第一层含义。
1.3.getMappingPathPatterns
接着回答第二个问题:getMappingPathPatterns 得到了怎样的集合?
答案:返回 Controller 类上作用于类的 @RequestMapping 注解和 Controller 类中方法上的作用于方法的 @RequestMapping 注解的结合。
追问1:为什么 RequestMapping 既可以是作用于类的注解,又可以是作用于方法的注解?
image.png
奥秘就在于 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 个:
image.png
追问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 能获取到结果,就意味着“命中索引”,效率就会高很多。
否则就只能“全表搜索”了,效率就会低很多。
来看一下源码:
image.png
但是,无论是哪种方式,都会调用 addMatchingMappings。时序图如下:
![](https://img.haomeiwen.com/i26273155/f6e4ab77e514b64e.png)
addMatchingMappings 方法中,foreach 遍历第一个参数——— RequestMappingInfo 集合 mappings。
-
getMatchingMapping:如果 RequestMappingInfo 和 HttpServletRequest 可以匹配,就返回一个新的 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。
-
第二步,完全匹配 RequestMappingInfo 和 HttpServletRequest。匹配逻辑在 RequestMappingInfo#getMatchingCondition 中。
-
第三步,对匹配结果 Match 排序,选出最佳的 HandlerMethod,排序比较的逻辑在 RequestMappingInfo#compareTo 中。
网友评论