Spring AntPathMatcher 实现原理
作者:互联网
最近在看Spring 源码,源码中多次出现AntPatternMatcher 类, 此类作用用于实现对URL的匹配
关于此类的介绍,其中有些代码是从Apache Ant 那 "借"过来的
PathMatcher implementation for Ant-style path patterns. Examples are provided below.
Part of this mapping code has been kindly borrowed from Apache Ant.
The mapping matches URLs using the following rules:
- ? matches one character
- * matches zero or more characters
- ** matches zero or more 'directories' in a path
Some examples:
-
com/t?st.jsp
- matchescom/test.jsp
but alsocom/tast.jsp
orcom/txst.jsp
-
com/*.jsp
- matches all.jsp
files in thecom
directory -
com/**/test.jsp
- matches alltest.jsp
files underneath thecom
path -
org/springframework/**/*.jsp
- matches all.jsp
files underneath theorg/springframework
path -
org/**/servlet/bla.jsp
- matchesorg/springframework/servlet/bla.jsp
but alsoorg/springframework/testing/servlet/bla.jsp
andorg/servlet/bla.jsp
看一下其核心的方法:
/**
* Actually match the given <code>path</code> against the given <code>pattern</code>.
* @param pattern the pattern to match against
* @param path the path String to test
* @param fullMatch whether a full pattern match is required (else a pattern match
* as far as the given base path goes is sufficient)
* @return <code>true</code> if the supplied <code>path</code> matched, <code>false</code> if it didn't
*/
protected boolean doMatch(String pattern, String path, boolean fullMatch,
Map<String, String> uriTemplateVariables) {
if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
return false;
}
String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
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 patDir = pattDirs[pattIdxStart];
if ("**".equals(patDir)) {
break;
}
if (!matchStrings(patDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
return false;
}
pattIdxStart++;
pathIdxStart++;
}
if (pathIdxStart > pathIdxEnd) {
// Path is exhausted, only match if rest of pattern is * or **'s
if (pattIdxStart > pattIdxEnd) {
/** /a/b/c and /a/b/c/ ==>false **/
return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) :
!path.endsWith(this.pathSeparator));
}
if (!fullMatch) {
return true;
}
/** /a/b/c/* and /a/b/c/ ==>true /a/b/c/* and /a/b/c ==> false **/
if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
return true;
} /** /a/b/c/**/**/ and /a/b/c ==> true /a/b/c/**/a/ and /a/b/c ==>false **/
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.
return false;
}
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 patDir = pattDirs[pattIdxEnd];
if (patDir.equals("**")) {
break;
}
if (!matchStrings(patDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
return false;
}
pattIdxEnd--;
pathIdxEnd--;
}
if (pathIdxStart > pathIdxEnd) { /** /**/a/b/c and /a/b/c ==> true **/
// String is exhausted
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
} /** /a/b/c/d/**/**/e/f/g/h/**/i/j/k/**/l/m/n/**/o/p/q and /a/b/c/d/a/b/e/f/g/h/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q ==> 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;
}
}
return true;
}
其实现原理如下:
1) 先把Pattern 和要与之匹配的字符窜 Path 进行 转化为 String Array, 分隔符为 "/"
例如: /a/**/b/c/d/ ==>["a","**","b","c","d"]
String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator);
String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator);
int pattIdxStart = 0;
int pattIdxEnd = pattDirs.length - 1;
int pathIdxStart = 0;
int pathIdxEnd = pathDirs.length - 1;
2) 先匹配 Pattern 中"**" 前的元素 ,这里说的元素是指两数组的元素,
// Match all elements up to the first **
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String patDir = pattDirs[pattIdxStart];
if ("**".equals(patDir)) {
break;
}
if (!matchStrings(patDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
return false;
}
pattIdxStart++;
pathIdxStart++;
}
跳出这个loop的条件是,
1.pattIdxStart>pattIdxEnd (没有找到"**")
说明在还没有找到有 "**"之前Pattern 在循环中溢出,即Pattern的长度比Path的长度要短,则结果肯定不匹配,应直接返回false
else if (pattIdxStart > pattIdxEnd) {
// String not exhausted, but pattern is. Failure.
return false;
}
2.pathIdxStart > pathIdxEnd (没有找到"**")
则在还没有找到 "**" 之前 Path在循环中溢出, 即Path的长度比Pattern的短,或者两者长度一样,
如果长度一样,则比较两者的最后一个字符是否相等,因为在转化为Array的时候,最后的字符'/' 会被 省略,所以,数组的元素匹配不一定说明最后的一个字符会相等,这时就需要判断是否一样,如果一样,则返回true,如果不一样,则返回false,
第二种情况就是Pattern的长度比Path长, 那匹配成功的情况就只有两种, 一是Pattern以* 号或者是** 号结尾,其中如果 是Pattern是以* 号结尾, Pattern 必须比Path只大一个元素,而且Path 的最后一样字符需为"/",因为 * 号只代表 0-n 个字符, 并没有 代表 "Directory" 的意思, Path和Pattern 的Directory 的深度必须一样, 所以判定结果如果 Path是以 "/" 结尾 并而且Pattern只比Path的元素少一个* 号,则返回true
如果Pattern比Path多一个以上的元素, 则后面的元素必须为 "**" 号, 否则返回false
if (pathIdxStart > pathIdxEnd) {
// Path is exhausted, only match if rest of pattern is * or **'s
if (pattIdxStart > pattIdxEnd) {
return (pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) :
!path.endsWith(this.pathSeparator));
}
if (!fullMatch) {
return true;
}
if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
return true;
}
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.
return false;
}
else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
// Path start definitely matches due to "**" part in pattern.
return true;
}
3) 然后后匹配 Pattern "**" 之后的元素, 如果其中匹配不成功,则返回false
这里有一个特殊的情况, 就是在没有找到 ** 号之前, Path 字符串提前退出,
特例 /**/**/**/a/b/c 和 /a/b/c ==>true
/**/a/b/**/a/b/c 和 /a/b/c ==> false
则判断Pattern的pattIdxStart 到pattIdxEnd 之间是否全部为 ** 号, 如果不是 , 则返回false
// up to last '**'
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String patDir = pattDirs[pattIdxEnd];
if (patDir.equals("**")) {
break;
}
if (!matchStrings(patDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
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;
}
第二种情况, Pattern和Path都没提前溢出退出循环, 则属于 这种情况
/a/b/c/d/**/**/e/f/g/h/**/i/j/k/**/l/m/n/**/o/p/q and /a/b/c/d/a/b/e/f/g/h/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q ==> true
这时候要处理多个 ** 号的, 分段处理, ** 号之间的字符匹配, 如果 ** 号之间的字符不匹配, 则返回false
第一段:
/a/b/c/d/**/**/e/f/g/h/**/
and
a/b/e/f/g/h/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q
==>true
第二段:
/**/i/j/k/**/l
and
/a/b/i/j/k/a/b/l/m/n/a/b/o/p/q
==>true
第三段:
/**/l/m/n/**/
/a/b/l/m/n/a/b/o/p/q
==>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;
}
转载于:https://my.oschina.net/iqoFil/blog/221620
标签:Path,true,Spring,AntPathMatcher,pattIdxStart,Pattern,pattern,path,原理 来源: https://blog.csdn.net/cichaojiao4477/article/details/100984708