Spring使用的是AntPathMatcher,进行模式匹配,遵循如下规则:
? 匹配一个字符
* 匹配零或者多个字符
** 匹配零或者多个目录
{spring:[a-z]+} 将正则表达式[a-z]+匹配到的值,赋值给名为spring的路径变量.
对阵AntPathMatcherTests的测试方法,中会有比较全面的测试用例,可以先参考
核心的匹配方法
这个方法,如果给我们普通人写,觉得是那种两行代码写出十个bug的,逻辑甚是复杂啊。
@Override
public boolean match(String pattern, String path) {
return doMatch(pattern, path, true, null);
}
protected boolean doMatch(String pattern, @Nullable String path, boolean fullMatch,
@Nullable Map<String, String> uriTemplateVariables) {
// 如果path 为null返回false
// 如果一个以"/"开头,一个不是,则发挥false
if (path == null || path.startsWith(this.pathSeparator)
!= pattern.startsWith(this.pathSeparator)) {
return false;
}
// 将给定的pattern,分组 比如path/we/{1}/* => ["path","we","{1}","*"]
String[] pattDirs = tokenizePattern(pattern);
//isPotentialMatch 判断是不是潜在匹配,也就是包含通配符,在WILDCARD_CHARS中
if (fullMatch && this.caseSensitive && !isPotentialMatch(path, pattDirs)) {
return false;
}
// 包含通配符,则进行通配符匹配,比如有一个请求/id/1/test,分解成 ["id","1","test"]
String[] pathDirs = tokenizePath(path);
int pattIdxStart = 0;
int pattIdxEnd = pattDirs.length - 1;
int pathIdxStart = 0;
int pathIdxEnd = pathDirs.length - 1;
// Match all elements up to the first **
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String pattDir = pattDirs[pattIdxStart];
// 如果pattern包含"**"直接过
if ("**".equals(pattDir)) {
break;
}
// 判断是否匹配,如果不匹配,直接返回
if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
return false;
}
pattIdxStart++;
pathIdxStart++;
}
// 已经匹配完了, 比如 path= /aa/bb pattern = /aa/bb
// 除非是pattern后面是/* 或者 /** 否则返回false
if (pathIdxStart > pathIdxEnd) {
// Path is exhausted, only match if rest of pattern is * or **'s
// 路径已用尽,仅当模式的其余部分为*或**'s时才匹配
if (pattIdxStart > pattIdxEnd) {
// 如果有一个@GetMapping("{id}/one/") 方法,
// 则必须用xx/one/请求,才返回,用xx/one是不行的
return (pattern.endsWith(this.pathSeparator) ==
path.endsWith(this.pathSeparator));
}
if (!fullMatch) {
return true;
}
if (pattIdxStart == pattIdxEnd
&& pattDirs[pattIdxStart].equals("*")
&& path.endsWith(this.pathSeparator)) {
return true;
}
// 如果剩余的,都不是** 则返回false,
// 否则返回true @GetMapping("/one/*/**") 有一个请求 /one/
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
}
else if (pattIdxStart > pattIdxEnd) {
// String not exhausted, but pattern is. Failure.
// 字符串没有用尽,但模式是失败,来了一个请求 @GetMapping("/") 来了个/a/two/请求
return false;
}
// 如果是 ** 开头 @GetMapping("**")
else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
// Path start definitely matches due to "**" part in pattern.
return true;
}
// up to last '**' 到最后的**
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String pattDir = pattDirs[pattIdxEnd];
if (pattDir.equals("**")) {
break;
}
if (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
// 路径:/1231/aa/bb/dd @GetMapping("/**/aa/bb")
return false;
}
pattIdxEnd--;
pathIdxEnd--;
}
if (pathIdxStart > pathIdxEnd) {
// String is exhausted
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
}
while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
int patIdxTmp = -1;
for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
if (pattDirs[i].equals("**")) {
patIdxTmp = i;
break;
}
}
if (patIdxTmp == pattIdxStart + 1) {
// '**/**' situation, so skip one
pattIdxStart++;
continue;
}
// Find the pattern between padIdxStart & padIdxTmp in str between
// strIdxStart & strIdxEnd
int patLength = (patIdxTmp - pattIdxStart - 1);
int strLength = (pathIdxEnd - pathIdxStart + 1);
int foundIdx = -1;
strLoop:
for (int i = 0; i <= strLength - patLength; i++) {
for (int j = 0; j < patLength; j++) {
String subPat = pattDirs[pattIdxStart + j + 1];
String subStr = pathDirs[pathIdxStart + i + j];
if (!matchStrings(subPat, subStr, uriTemplateVariables)) {
continue strLoop;
}
}
foundIdx = pathIdxStart + i;
break;
}
if (foundIdx == -1) {
return false;
}
pattIdxStart = patIdxTmp;
pathIdxStart = foundIdx + patLength;
}
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
// 比如 :/1231/aa/bb/d/aa/bb/和**/aa/bb/
return true;
}
当匹配多个的时候,如何选择
当多个模式与请求URI匹配时,必须将它们进行比较以找到最佳匹配。 通过找到最具体的匹配来完成。 计算分数以找到最具体的匹配项。 最低分获胜。 应用以下规则:
- 单个通配符(*)被视为一分。
- 双通配符(**)被视为两分。
- 没有任何模式(例如
/cars/ dealer
)的URI路径计数为零分。一个’?'被计数为零分(例如/ car?/d??r的总计数为零)。 /card/{type}
计为一分。- 如果只有
/ **
即@RequestMapping("/**")
,则始终在末尾进行匹配。在没有双通配符的模式之后,还会匹配/employee/**
之类的patterns(前缀模式,以/ **结尾的模式)。 - 在Mapping变量中带有
*
的正则表达式(例如/card/{id:n.*}
也被视为一分。而其他正则表达式量词(+或?
)零分。 - 如果多个模式具有相等的分数,则选择更长的模式。此处的长度是通过计算的String#length()。对于Mapping,在计算长度之前,{}之间的内容会减少为单个#(例如,将
/card/{var1}/{var2}
更改为/card/#/#
)。 - 此时,如果两个模式的长度相同,则选择通配符(*)较少的模式。
- 此时,如果两个模式具有相同数量的通配符(*),则选择模板变量数量较少的模式。
以上逻辑是在AntPathStringMatcher#compare方法中实现的(此类是的嵌套类AntPathMatcher)。