一、接口继承图
二、ResourcePatternResolver接口
ResourcePatternResolver接口继承自ResourceLoader接口,增加了Resource[] getResources(String locationPattern)方法,即根据ANT风格的路径表达式返回多个匹配的资源,默认实现是PathMatchingResourcePatternResolver类。PathMatchingResourcePatternResolver类中判断ANT风格的路径表达式与目标路径是否匹配通过AntPathMatcher类实现,重点关注AntPathMatcher类的 boolean doMatch(String pattern, String path, boolean fullMatch,@Nullable Map
protected boolean doMatch(String pattern, String path, boolean fullMatch,
@Nullable Map uriTemplateVariables) {
if (path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) {
return false;
}
String[] pattDirs = tokenizePattern(pattern);
//isPotentialMatch表示是否可能完全匹配,isPotentialMatch只做简单的字符比较
if (fullMatch && this.caseSensitive && !isPotentialMatch(path, pattDirs)) {
return false;
}
String[] pathDirs = tokenizePath(path);
int pattIdxStart = 0;
int pattIdxEnd = pattDirs.length - 1;
int pathIdxStart = 0;
int pathIdxEnd = pathDirs.length - 1;
//逐一匹配所有的patt,直到第一个**
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String pattDir = pattDirs[pattIdxStart];
if ("**".equals(pattDir)) {
break;
}
//将ant表达式转换成正则表达式匹配
if (!matchStrings(pattDir, pathDirs[pathIdxStart], uriTemplateVariables)) {
return false;
}
pattIdxStart++;
pathIdxStart++;
}
//pathDirs中所有的path都匹配过了
if (pathIdxStart > pathIdxEnd) {
// pattDirs都比较完了,判断pattern和path是否都以路径分隔符结尾
if (pattIdxStart > pattIdxEnd) {
return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator));
}
//pattDirs未比较完,如果是非完全匹配则返回true
if (!fullMatch) {
return true;
}
//pattDirs比pattDirs多一个,且最后一个是*,path以路径分隔符结尾
if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart].equals("*") && path.endsWith(this.pathSeparator)) {
return true;
}
//pattDirs中未比较的patt只能是**
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
}
else if (pattIdxStart > pattIdxEnd) {
// pattDirs比较完了而pathDirs未比较完,完全匹配失败
return false;
}
else if (!fullMatch && "**".equals(pattDirs[pattIdxStart])) {
// pattDirs和pathDirs因为**跳出循环,如果不是完全匹配则返回true,即 pathDirs中未比较完的部分都可以当做**
return true;
}
// pattDirs和pathDirs因为**跳出循环,且此时是完全匹配
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String pattDir = pattDirs[pattIdxEnd];
if (pattDir.equals("**")) {
break;
}
//倒着匹配非**部分,匹配失败返回false
if (!matchStrings(pattDir, pathDirs[pathIdxEnd], uriTemplateVariables)) {
return false;
}
pattIdxEnd--;
pathIdxEnd--;
}
if (pathIdxStart > pathIdxEnd) {
// pathDirs倒着匹配完成,pattDirs剩余的中间部分必须是**
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
//两个**中间的部分,所以要减1
int patLength = (patIdxTmp - pattIdxStart - 1);
//待比较的str包含pathIdxEnd和pathIdxStart两个,所以加1
int strLength = (pathIdxEnd - pathIdxStart + 1);
int foundIdx = -1;
strLoop:
//循环比较pathIdxEnd和pathIdxStart中间的部分是否存在跟patt匹配的地方,如果存才则跳出循环
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)) {
//跳出外层for循环
continue strLoop;
}
}
foundIdx = pathIdxStart + i;
break;
}
if (foundIdx == -1) {
return false;
}
pattIdxStart = patIdxTmp;
pathIdxStart = foundIdx + patLength;
}
//至此两个**之间的表达式都匹配完成了,pattDirs中剩余的未匹配部分只能是**
for (int i = pattIdxStart; i <= pattIdxEnd; i++) {
if (!pattDirs[i].equals("**")) {
return false;
}
}
return true;
}
//将ant风格的表达式转换成正则表达式字符串
public AntPathStringMatcher(String pattern, boolean caseSensitive) {
StringBuilder patternBuilder = new StringBuilder();
Matcher matcher = GLOB_PATTERN.matcher(pattern);
int end = 0;
while (matcher.find()) {
//quote方法是将正则表达式转换成普通的字符串
patternBuilder.append(quote(pattern, end, matcher.start()));
String match = matcher.group();
if ("?".equals(match)) {
patternBuilder.append('.');
}
else if ("*".equals(match)) {
patternBuilder.append(".*");
}
//匹配形如com/{filename:\\w+}.jsp的表达式, 匹配字符串com/test.jsp时会将test作为filename的变量值
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));
}
参考测试用例如下:
@Test
public void matchTest() {
assertFalse(pathMatcher.match("/x/x/**/bla", "/x/x/x/"));
assertTrue(pathMatcher.matchStart("/x/x/**/bla", "/x/x/x"));
assertFalse(pathMatcher.matchStart("/x/x/**/bla", "/a/x/x"));
assertTrue(pathMatcher.matchStart("/x/x/**/bla", "/x/x/b"));
assertTrue(pathMatcher.matchStart("/x/x/**/bla", "/x/x"));
assertTrue(pathMatcher.matchStart("/x/x/**/bla", "/x/x/"));
assertTrue(pathMatcher.matchStart("/x/x/**/bla", "/x"));
assertTrue(pathMatcher.matchStart("/x/x/**/bla", "/x/x/x/c"));
assertTrue(pathMatcher.matchStart("*/x/x/**/bla", "bb/x"));
assertFalse(pathMatcher.matchStart("*/x/", "bb/x"));
assertTrue(pathMatcher.matchStart("*/x/c", "bb/x"));
assertTrue(pathMatcher.match("/x/c/*", "/x/c/"));
assertTrue(pathMatcher.match("/x/c/**", "/x/c/"));
assertFalse(pathMatcher.match("/x/c/**/b", "/x/c/"));
assertFalse(pathMatcher.match("/x/c/**/*", "/x/c/"));
assertFalse(pathMatcher.match("/x/c/**/c/*/b/**/b", "/x/c/a/b/d/c/b/c/d/e/b"));
assertTrue(pathMatcher.match("/x/c/**/c/*/b/**/b", "/x/c/a/b/d/c/d/b/c/d/e/b"));
}
PathMatchingResourcePatternResolver通过ResourceLoader实例实现ResourceLoader接口,Resource[] getResources(String locationPattern)接口借助ResourceLoader接口实现,基本思路是将路径表达式拆分成不包含通配符的根路径和包含通配符的子路径,利用ResourceLoader实例读取根路径下所有的文件,将文件路径逐一与包含通配符的子路径匹配,匹配则返回该资源。ResourceLoader接口实现可以读取classpath:开头的资源,PathMatchingResourcePatternResolver在此基础上增加了对classpath*:的资源的读取能力,即在类路径下所有的jar包,zip包等中搜索目标文件,核心代码注释如下:
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// 判断是否包含路径表达式
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
// 通过ClassLoader加载指定路径下所有的文件
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Generally only look for a pattern after a prefix here,
// and on Tomcat only after the "*/" separator for its "war:" protocol.
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
//普通的非classpath*:的路径通过ResourceLoader加载即可
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
//不包含通配符的明确的根路径
String rootDirPath = determineRootDir(locationPattern);
//包含通配符的子路径
String subPattern = locationPattern.substring(rootDirPath.length());
//找到根路径下所有的资源
Resource[] rootDirResources = getResources(rootDirPath);
Set result = new LinkedHashSet<>(16);
for (Resource rootDirResource : rootDirResources) {
rootDirResource = resolveRootDirResource(rootDirResource);
URL rootDirUrl = rootDirResource.getURL();
//对OSGI类资源做特殊处理
if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
if (resolvedUrl != null) {
rootDirUrl = resolvedUrl;
}
rootDirResource = new UrlResource(rootDirUrl);
}
//jboss vfs资源特殊处理
if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
}
else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
//读取jar包的所有文件,逐一比较jar包中的文件名是否匹配路径表达式
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
}
else {
//读取文件系统某个目录下所有的文件,逐一比较文件名是否匹配路径表达式
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isTraceEnabled()) {
logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[0]);
}
三、PropertySourceFactory接口
该接口比较简单,只有一个方法,PropertySource> createPropertySource(@Nullable String name, EncodedResource resource),其中EncodedResource是对Resource的简单包装,增加了具体编码的属性,即根据name和Resource创建一个PropertySource实例。
默认实现类只有一个,DefaultPropertySourceFactory,返回ResourcePropertySource实例,该对象继承自PropertiesPropertySource类,具体实现是通过DefaultResourceLoader将资源位置location转换成Resource实例,将EncodedResource或者Resource通过PropertiesLoaderUtils转换成Properties,利用PropertiesPropertySource的构造方法实例化。
PropertiesLoaderUtils提供了通过EncodedResource 或者 Resource或者 resourceName加载配置属性,其中通过resourceName加载时通过ClassLoader读取资源, 具体实现是先通过EncodedResource 或者 Resource获取资源文件的输入流InputStream或者Reader,再利用PropertiesPersister实例加载到Properties中。
PropertiesPersister接口包含读取属性配置,将属性配置写入到文件中的多个方法,只有一个默认实现类,DefaultPropertiesPersister,该类简单利用Properties类本身的读取和写入属性配置的方法实现接口。