Spring源码之AntPathMatcher(二):内部类AntPathStringMatcher

Spring源码之AntPathMatcher二:matchStrings-getStringMatcher-AntPathStringMatcher


上一篇文章 Spring源码之AntPathMatcher的doMatch算法写了AntPathMatcher类的doMatch方法将模式串pattern和字符串path以“/”分隔成数组,然后通过对应的算法,将各个数组取到的值进行匹配,其中就用到了matchStrings,比如下面用matchStrings:

while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {  //从第一个元素同时开始,对应匹配
	String pattDir = pattDirs[pattIdxStart];
	if ("**".equals(pattDir)) {//遇到模式串中的**就跳过,因为**表示匹配0个或多个目录
		break;
	}
	if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {//matchStrings其内部实现就是将Ant风格的模式串 pattDir 转为正则表达式然后去和 pathDirs[pathIdxStart] 做匹配,匹配不上返回false
		return false;
	}
	pattIdxStart++;
	pathIdxStart++;
}

其中的matchStrings的作用就是将含有Ant风格通配符的模式串 pattDir 转为正则表达式然后去和 pathDirs[pathIdxStart] 做匹配,
什么是Ant风格的通配符呢?其实就是下面四种:

通配符含义例子
匹配任何单字符/aa/b?.jsp表示所有aa目录下的bX.jsp文件,比如/aa/bc.jsp
*匹配0或者任意数量的字符/aa/b*.jsp表示所有aa目录下的bXXX.jsp文件,比如/aa/bcd.jsp,或者/aa/b.jsp
**匹配0或者更多的目录/aa/**/bb.jsp表示所有aa目录下所有含有bb.jsp文件的目录,比如/aa/dd/bb.jsp或者/aa/ee/dd/bb.jsp
{variableName:[a-z]+}配置正则表达式 [a-z]+ 作为variableName的值/aa/{filename:\w+}.jsp}将匹配/aa/test.jsp,并将test分配给filename变量(这个是springMvc4.3的新特性)

Ant通配符最长匹配原则:/aa/bb/cc/dd.jsp同时符合下面两种模式的情况:/aa/**/*.jsp和/aa/bb/cc/d? .jsp。这种情况按照最长匹配原则由/aa/bb/cc/d? .jsp匹配。
好了,继续。通过源码我们可以看到matchStrings其实调用了getStringMatcher方法:

private boolean matchStrings(String pattern, String str,
		@Nullable Map<String, String> uriTemplateVariables) {
	return getStringMatcher(pattern).matchStrings(str, uriTemplateVariables);//getStringMatcher其内部实现就是将Ant风格的模式串转为正则表达式然后去和参数str做匹配
}

getStringMatcher方法返回的其实是一个AntPathStringMatcher对象,然后调用AntPathStringMatcher.matchStrings方法返回最终结果true或false。
getStringMatcher方法源码如下:

protected AntPathStringMatcher getStringMatcher(String pattern) {  //其内部实现就是将Ant风格的模式串转为正则表达式然后去和str做匹配
	AntPathStringMatcher matcher = null;
	Boolean cachePatterns = this.cachePatterns;
	if (cachePatterns == null || cachePatterns.booleanValue()) {
		matcher = this.stringMatcherCache.get(pattern);//从缓存中查找,是否为null
	}
	if (matcher == null) { //缓存中没有这个pattern就去创建一个
		matcher = new AntPathStringMatcher(pattern, this.caseSensitive);
		if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) {
			// Try to adapt to the runtime situation that we're encountering:
			// There are obviously too many different patterns coming in here...
			// So let's turn off the cache since the patterns are unlikely to be reoccurring.
			deactivatePatternCache();
			return matcher;
		}
		if (cachePatterns == null || cachePatterns.booleanValue()) {
			this.stringMatcherCache.put(pattern, matcher);//将自己新建的matcher加入缓存中,如果下次用到这个pattern,就不用新建了,增加效率
		}
	}
	return matcher;
}

通过源码可以看出getStringMatcher先从缓存中查找是否已经有了这个pattern被转换了如果有就不去做剩下的操作了,直接用缓存中的,提高效率,

matcher = this.stringMatcherCache.get(pattern);//从缓存中查找,是否为null

如果没有就去创建一个,然后加入缓存中

matcher = new AntPathStringMatcher(pattern, this.caseSensitive);
。。。
his.stringMatcherCache.put(pattern, matcher);//将自己新建的matcher加入缓存中,如果下次用到这个pattern,就不用新建了,增加效率

我们可以看到new AntPathStringMatcher时传入了模式串和是否区分大小写的参数

new AntPathStringMatcher(pattern, this.caseSensitive)

下面看内部类AntPathStringMatcher,是怎么将Ant风格通配符转为正则表达式的并进行匹配的,其构造函数如下

public AntPathStringMatcher(String pattern, boolean caseSensitive) {
	StringBuilder patternBuilder = new StringBuilder();
	Matcher matcher = GLOB_PATTERN.matcher(pattern);//GLOB_PATTERN正则表达式和模式串pattern匹配,会得到一个matcher,matcher中包含一些方法,得到匹配的一些情况
	int end = 0;
	while (matcher.find()) {
		patternBuilder.append(quote(pattern, end, matcher.start()));//在找到匹配的情况下将前面的字符串先放入patternBuilder中
		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) {  //如果没有包含':'直接就加DEFAULT_VARIABLE_PATTERN:(.*)
				patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
				this.variableNames.add(matcher.group(1));//matcher.group(1)表示GLOB_PATTERN中的分组捕获的第一个
			}
			else {//如果{}含有":",则将{}中的内容拆分下,
				String variablePattern = match.substring(colonIdx + 1, match.length() - 1);//取":"后面的字符串
				patternBuilder.append('(');
				patternBuilder.append(variablePattern);
				patternBuilder.append(')');
				String variableName = match.substring(1, colonIdx);//取变量名,比如常见的classpath:mapper/*.xml中的classpath
				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));//最终生成一个正则表达式,caseSensitive用来区分大小写的
}

这也是核心的地方,首先将传入的Ant风格的pattern模式串与定义的GLOB_PATTERN做一次匹配matcher:

Matcher matcher = GLOB_PATTERN.matcher(pattern);//GLOB_PATTERN正则表达式和模式串pattern匹配,会得到一个matcher,matcher中包含一些方法,得到匹配的一些情况

其中的GLOB_PATTERN就是我们编译的一个模式串对象

private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}");//第一个"\"都是转义"\"的,()的内部是一个分组

将这个模式串与传参pattern进行matcher得到一个Matcher对象,这个对象包含匹配的一些情况,比如matcher.find()为true就说明这个模式字符串pattern和GLOB_PATTERN是匹配到的,再比如matcher.group()得到的是匹配的那个字符,例如“?”匹配到了,这个时候matcher.group()就是“?”。
那么继续,如果匹配到了,也就是matcher.find()为true。就先将匹配到的字符串之前的不是通配符的字符串放入建好的StringBuilder中:

StringBuilder patternBuilder = new StringBuilder();
。。。
patternBuilder.append(quote(pattern, end, matcher.start()));//在找到匹配的情况下,比如将?之前的字符串先放入patternBuilder中

这个patternBuilder,最终就是我们转为正则表达式的一个字符串。
好,继续,匹配到了之后呢,如果匹配到的字符String match = matcher.group()是“?”,就替换成正则表达式中对应的’.’:

if ("?".equals(match)) {	 //如果找到了?就转换为正则表达式中的'.'
	patternBuilder.append('.');
}

如果是“*”就是替换成正则表达式中的".*"

else if ("*".equals(match)) { //如果找到了*就转换为正则表达式中的'.*'
		patternBuilder.append(".*");
}

如果是花括号包住的,就先判断是不是带有“:”,如果没有就直接转换成正则表达式中的"(.*)",同时将变量名提取出来,

this.variableNames.add(matcher.group(1));//matcher.group(1)表示GLOB_PATTERN中的分组捕获的第一个

如果有“:”,比如{path:\w+},这个时候就需要通过“:”分隔开来,将“:”后面的正则表达式\w+放在()中,然后提取变量名

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);
}

最后,将模式串pattern尾部不是通配符的字符串放入patternBuilder中,然后最终生成一个正则表达式this.pattern

patternBuilder.append(quote(pattern, end, pattern.length()));//将模式串pattern尾部不是通配符的字符串放入patternBuilder中
this.pattern = (caseSensitive ? Pattern.compile(patternBuilder.toString()) :
		Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE));//最终生成一个正则表达式,caseSensitive用来区分大小写的

其中quote方法是用来将不是通配符的字符串做为字面常量来处理,以提高效率,比如传入的模式串为aa{path}cc}其中“}”在正则表达式中属于特殊字符,正则表达式会做一些判断,但是经过quote处理之后,就做为字面常量来处理,而不是做特殊字符处理了,最终模式串aa{path}cc}会被转换为下面这个正则表达式

\Qaa\E(.*)\Qcc}\E         被\Q  \E包裹的就做为字面常量处理,增加效率	

通过上面的一系列操作,就可以将传入的pattern模式串转换为正则表达式,放入this.pattern中,

this.pattern = (caseSensitive ? Pattern.compile(patternBuilder.toString()) :Pattern.compile(patternBuilder.toString(), Pattern.CASE_INSENSITIVE));//最终生成一个正则表达式,caseSensitive用来区分大小写的

在回来看matchStrings方法,getStringMatcher方法的到一个内部类AntPathStringMatcher对象,然后调用它的方法matchStrings

private boolean matchStrings(String pattern, String str,
		@Nullable Map<String, String> uriTemplateVariables) {

	return getStringMatcher(pattern).matchStrings(str, uriTemplateVariables);//getStringMatcher其内部实现就是将Ant风格的模式串转为正则表达式然后去和参数str做匹配
}

matchStrings方法如下:

public boolean matchStrings(String str, @Nullable Map<String, String> uriTemplateVariables) {  //变量解析
	Matcher matcher = this.pattern.matcher(str);
	if (matcher.matches()) { //如果不匹配直接返回false,如果不返回就
		if (uriTemplateVariables != null) {
			// SPR-8455
			if (this.variableNames.size() != matcher.groupCount()) {
				throw new IllegalArgumentException("The number of capturing groups in the pattern segment " +
						this.pattern + " does not match the number of URI template variables it defines, " +
						"which can occur if capturing groups are used in a URI template regex. " +
						"Use non-capturing groups instead.");
			}
			for (int i = 1; i <= matcher.groupCount(); i++) {
				String name = this.variableNames.get(i - 1);
				String value = matcher.group(i);
				uriTemplateVariables.put(name, value);
			}
		}
		return true;
	}
	else {
		return false;
	}
}

可以看到,如果不匹配直接返回false,如果匹配返回true,如果uriTemplateVariables != null,就去提取模版变量variableNames。
至此就是matchStrings的实现,最终就是通过AntPathStringMatcher将传入的pattern模式串(这个模式串是Ant风格的,也可能含有正则表达式,比如上文的{path:\w+}中的\w+)中对应的通配符转换成正则表达式,然后与传参路径字符串进行匹配。注:大家最好跟着源码一起看本篇文章,或者自己写例子调试,一步一步,就可以分析清楚了。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值