result = new LinkedHashSet<>(16);
for (Resource rootDirResource : rootDirResources) {
rootDirResource = resolveRootDirResource(rootDirResource);
URL rootDirUrl = rootDirResource.getURL();
// bundle 资源类型
if (equinoxResolveMethod != null && rootDirUrl.getProtocol().startsWith("bundle")) {
URL resolvedUrl = (URL) ReflectionUtils.invokeMethod(equinoxResolveMethod, null, rootDirUrl);
if (resolvedUrl != null) {
rootDirUrl = resolvedUrl;
}
rootDirResource = new UrlResource(rootDirUrl);
}
// vfs 资源类型
if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
// jar 资源类型
} else if (ResourceUtils.isJarURL(rootDirUrl) || isJarResource(rootDirResource)) {
result.addAll(doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPattern));
// 其它资源类型
} else {
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isTraceEnabled()) {
logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
// 转换成 Resource 数组返回
return result.toArray(new Resource[0]);
}
```
方法有点儿长,但是思路还是很清晰的,主要分两步:
1. 确定目录,获取该目录下得所有资源。
2. 在所获得的所有资源后,进行迭代匹配获取我们想要的资源。
------
在这个方法里面,我们要关注两个方法,一个是 `#determineRootDir(String location)` 方法,一个是 `#doFindPathMatchingXXXResources(...)` 等方法。
##### 2.5.5.1 determineRootDir
`determineRootDir(String location)` 方法,主要是用于确定根路径。代码如下:
```
/**
* Determine the root directory for the given location.
* Used for determining the starting point for file matching,
* resolving the root directory location to a {@code java.io.File}
* and passing it into {@code retrieveMatchingFiles}, with the
* remainder of the location as pattern.
*
Will return "/WEB-INF/" for the pattern "/WEB-INF/*.xml",
* for example.
* @param location the location to check
* @return the part of the location that denotes the root directory
* @see #retrieveMatchingFiles
*/
protected String determineRootDir(String location) {
// 找到冒号的后一位
int prefixEnd = location.indexOf(':') + 1;
// 根目录结束位置
int rootDirEnd = location.length();
// 在从冒号开始到最后的字符串中,循环判断是否包含通配符,如果包含,则截断最后一个由”/”分割的部分。
// 例如:在我们路径中,就是最后的ap?-context.xml这一段。再循环判断剩下的部分,直到剩下的路径中都不包含通配符。
while (rootDirEnd > prefixEnd && getPathMatcher().isPattern(location.substring(prefixEnd, rootDirEnd))) {
rootDirEnd = location.lastIndexOf('/', rootDirEnd - 2) + 1;
}
// 如果查找完成后,rootDirEnd = 0 了,则将之前赋值的 prefixEnd 的值赋给 rootDirEnd ,也就是冒号的后一位
if (rootDirEnd == 0) {
rootDirEnd = prefixEnd;
}
// 截取根目录
return location.substring(0, rootDirEnd);
}
```
方法比较绕,效果如下示例:
| 原路径 | 确定根路径 |
| :--------------------------------: | :-------------------: |
| `classpath*:test/cc*/spring-*.xml` | `classpath*:test/` |
| `classpath*:test/aa/spring-*.xml` | `classpath*:test/aa/` |
##### 2.5.5.2 doFindPathMatchingXXXResources
`#doFindPathMatchingXXXResources(...)` 方法,是个泛指,一共对应三个方法:
- `#doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPatter)` 方法
- `#doFindPathMatchingFileResources(rootDirResource, subPattern)` 方法
- `VfsResourceMatchingDelegate#findMatchingResources(rootDirUrl, subPattern, pathMatcher)` 方法
因为本文重在分析 Spring 统一资源加载策略的整体**流程**。相对来说,上面几个方法的代码量会比较多。所以本文不再追溯,感兴趣的胖友,推荐阅读如下文章:
- [《Spring源码情操陶冶-PathMatchingResourcePatternResolver路径资源匹配溶解器》](https://www.cnblogs.com/question-sky/p/6959493.html) ,主要针对 `#doFindPathMatchingJarResources(rootDirResource, rootDirUrl, subPatter)` 方法。
- [《深入 Spring IoC 源码之 ResourceLoader》](http://www.blogjava.net/DLevin/archive/2012/12/01/392337.html) ,主要针对 `#doFindPathMatchingFileResources(rootDirResource, subPattern)` 方法。
- [《Spring 源码学习 —— 含有通配符路径解析(上)》](http://www.coderli.com/spring-wildpath-parse/) 😈 貌似没有下
## 3. 小结
至此 Spring 整个资源记载过程已经分析完毕。下面简要总结下:
- Spring 提供了 Resource 和 ResourceLoader 来统一抽象整个资源及其定位。使得资源与资源的定位有了一个更加清晰的界限,并且提供了合适的 Default 类,使得自定义实现更加方便和清晰。
- AbstractResource 为 Resource 的默认抽象实现,它对 Resource 接口做了一个统一的实现,子类继承该类后只需要覆盖相应的方法即可,同时对于自定义的 Resource 我们也是继承该类。
- DefaultResourceLoader 同样也是 ResourceLoader 的默认实现,在自定 ResourceLoader 的时候我们除了可以继承该类外还可以实现 ProtocolResolver 接口来实现自定资源加载协议。
- DefaultResourceLoader 每次只能返回单一的资源,所以 Spring 针对这个提供了另外一个接口 ResourcePatternResolver ,该接口提供了根据指定的 locationPattern 返回多个资源的策略。其子类 PathMatchingResourcePatternResolver 是一个集大成者的 ResourceLoader ,因为它即实现了 `Resource getResource(String location)` 方法,也实现了 `Resource[] getResources(String locationPattern)` 方法。
另外,如果胖友认真的看了本文的包结构,我们可以发现,Resource 和 ResourceLoader 核心是在,`spring-core` 项目中。
如果想要调试本小节的相关内容,可以直接使用 Resource 和 ResourceLoader 相关的 API ,进行操作调试。