这个类实现PathMatcher
这个接口.
boolean isPattern(string path)
: 用来检查给定的**path(pattern–匹配规则)**是否是个合法的匹配规则.boolean match(string pattern, string path)
:依据pattern的匹配策略来匹配path是否符合规则.boolean matchStart(String pattern, String path)
:确定该模式是否至少与给定的基本路径匹配String extractPathWithinPattern(String pattern, String path)
:剥离匹配模式的前导静态部分,只返回动态部分匹配的path.
例如:
pattern = "myroot/*.html" ; path = "myroot/myfile.html"
会返回myfile.html
Map extractUriTemplateVariables(String pattern, String path)
:匹配URI,并返回URI参数map;
例如:
pattern = "/hotels/{hotel}" path = "/hotels/1"
那么返回的map包含hotel->1
Comparator getPatternComparator(String path)
:根据完整路径,返回一个适配匹配模式匹配路径排序的比较器String combine(String pattern1, String pattern2)
: 整合两个模式,合并成1个新模式并返回@Override
public boolean isPattern(String path) {
return (path.indexOf('*') != -1 || path.indexOf('?') != -1);
}
包含*
或者?
就代表这个path是个匹配模式
首先看一下这个方法的调用时序图
默认分隔符是/
,首先 path 如果以分隔符开始 那么pattern必须也以分隔符开始,否则返回false
if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
return false;
}
使用分隔符把pattern分隔成string数组,观察下面代码使用了ConcurrentHashMap作为缓存分隔好的模式提升性能.
protected String[] tokenizePattern(String pattern) {
String[] tokenized = null;
Boolean cachePatterns = this.cachePatterns;
if (cachePatterns == null || cachePatterns.booleanValue()) {
tokenized = this.tokenizedPatternCache.get(pattern);
}
if (tokenized == null) {
tokenized = tokenizePath(pattern);
//当缓存大于65536这个阈值的时候讲不再缓存,并且清理缓存
if (cachePatterns == null && this.tokenizedPatternCache.size() >= CACHE_TURNOFF_THRESHOLD) {
deactivatePatternCache();
return tokenized;
}
if (cachePatterns == null || cachePatterns.booleanValue()) {
this.tokenizedPatternCache.put(pattern, tokenized);
}
}
return tokenized;
}
如果是全路径并且区分大小写,那么就检查path是否有潜在匹配的可能,如果没有,返回不匹配,反之亦然
if (fullMatch && this.caseSensitive && !isPotentialMatch(path, pattDirs)) {
return false;
}
isPotentialMatch 源代码
private boolean isPotentialMatch(String path, String[] pattDirs) {
//如果去除标记的空格设置true (默认false)直接返回成功
if (!this.trimTokens) {
int pos = 0;
for (String pattDir : pattDirs) {
//跳过分隔符并返回位置
int skipped = skipSeparator(path, pos, this.pathSeparator);
pos += skipped;
//检查模式中是否存在通配符('*', '?', '{'),存在跳出,不存在且字符匹配skipped+1,并返回skipped
skipped = skipSegment(path, pos, pattDir);
//如果小于模式的长度,意味着不全等,也可能是遇到通配符跳出了.. 所以只有skipped=0 并且没有通配符.才返回false
if (skipped < pattDir.length()) {
return (skipped > 0 || (pattDir.length() > 0 && isWildcardChar(pattDir.charAt(0))));
}
pos += skipped;
}
}
return true;
}
如果上面匹配了,接着使用分隔符把需要匹配的path分隔成string数组,这里面使用了java.util 里面的StringTokenizer来处理字符串字符.
public static String[] tokenizeToStringArray(
@Nullable String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
if (str == null) {
return new String[0];
}
StringTokenizer st = new StringTokenizer(str, delimiters);
List<String> tokens = new ArrayList<>();
while (st.hasMoreTokens()) {
String token = st.nextToken();
if (trimTokens) {
token = token.trim();
}
if (!ignoreEmptyTokens || token.length() > 0) {
tokens.add(token);
}
}
return toStringArray(tokens);
}
接着就是对这两个字符数组从左到右开始匹配…如果模式字符匹配到**
那么就意味着首部匹配通过.否则讲ant规则字符转换成对应的正则模式,然后用java的正则匹配.
?
表示正则的.
,看下面代码
// up to last '**'
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String pattDir = pattDirs[pattIdxStart];
if ("**".equals(pattDir)) {
break;
}
//转换规则
if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
return false;
}
pattIdxStart++;
pathIdxStart++;
}
//此时如果需要匹配的字符串全匹配了
if (pathIdxStart > pathIdxEnd) {
if (pattIdxStart > pattIdxEnd) {
return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator));
}
//如果不需要全匹配 就直接返回true
if (!fullMatch) {
return true;
}
// path 全匹配了..如果还存在模式的话 只能是* 和 ** 了
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) {
// 模式数组匹配完了,path没有匹配完的情况.失败
return false;
} else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
// 不需要全匹配的情况 .匹配到 ** 就是true
return true;
}
//然后从模式尾部开始一轮匹配(因为前面可能遇到**跳出来了,所以需要从尾部继续匹配)
`while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd)`
`if (pathIdxStart > pathIdxEnd)`
//要是尾部匹配还是遇到**break跳出了 ,只要模式前后不等,并且还有需要匹配的字符进入这个循环,否则直接判断模式接下来的字符只要不等于**就是不匹配
while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) {
//定义一个临时模式的起始位置
int patIdxTmp = -1;
//从pattIdxStart+1开始检查,因为前面的已经再上面检查过了.
for (int i = pattIdxStart + 1; i <= pattIdxEnd; i++) {
//i次时遇到**,临时模式位置变为i
if (pattDirs[i].equals("**")) {
patIdxTmp = i;
break;
}
}
if (patIdxTmp == pattIdxStart + 1) {
// '**/**' 出现这种情况的时候跳过1个
pattIdxStart++;
continue;
}
// 去中间的字符,和临时模式和结尾模式中间的模式字符
// 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
pattIdxStart = patIdxTmp;
//foundIdx(匹配到的字符位置) + patLength(剩余模式长度)赋值给pathIdxStart
pathIdxStart = foundIdx + patLength;
}
// patIdxTmp后面还有模式的话 都必须的是**
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
----------------------------------------------------
public AntPathStringMatcher(String pattern, boolean caseSensitive) {
StringBuilder patternBuilder = new StringBuilder();
Matcher matcher = GLOB_PATTERN.matcher(pattern);
int end = 0;
while (matcher.find()) {
patternBuilder.append(quote(pattern, end, matcher.start()));
String match = matcher.group();
if ("?".equals(match)) {
patternBuilder.append('.');
}
else if ("*".equals(match)) {
patternBuilder.append(".*");
}
else if (match.startsWith("{") && match.endsWith("}")) {
int colonIdx = match.indexOf(':');
if (colonIdx == -1) {
patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
this.variableNames.add(matcher.group(1));
}
else {
String variablePattern = match.substring(colonIdx + 1, match.length() - 1);
patternBuilder.append('(');
patternBuilder.append(variablePattern);
patternBuilder.append(')');
String variableName = match.substring(1, colonIdx);
this.variableNames.add(variableName);
}
}
end = matcher.end();
}
patternBuilder.append(quote(pattern, end, pattern.length()));
this.pattern = (caseSensitive ? Pattern.compile(patternBuilder.toString()) :
Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE));
}
未完待续…