本文简单的分析下spring对某个目录下的class资源是如何做到全部的加载

PathMatchingResourcePatternResolver#getResources

PathMatchingResourcePatternResolverResourcePatternResolver的实现类,主要实现的方法为getResources(String locationPattern),具体代码如下,以classpath*:com/question/**/*.class为例

public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
//判断查找路径是否以classpath*:开头
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 {
// all class path resources with the given name。找寻classpath路径下的根目录全路径,包含jar、zip包
//比如classpath*:com/question/
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
//一般此处针对classpath:开头的资源加载
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern 加载某个目录
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name优先加载classpath路径下的项目对应资源,找不到才查找jar、zip资源
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}

PathMatchingResourcePatternResolver#findPathMatchingResources()

protected方法,查找指定路径下的所有资源,同时支持zip、jar中资源的查找

protected Resource[] findPathMatchingResources(String locationPattern) throws IOException {
//首先定位根目录路径,例如classpath*:com/question/
String rootDirPath = determineRootDir(locationPattern);
//默认为**/*.class
String subPattern = locationPattern.substring(rootDirPath.length());
//递归函数的调用,此处会调用PathMatchingResourcePatternResolver#findAllClassPathResources方法加载根目录,找寻classpath路径下的根目录全路径,包含jar、zip包
Resource[] rootDirResources = getResources(rootDirPath); Set<Resource> result = new LinkedHashSet<Resource>(16);
for (Resource rootDirResource : rootDirResources) {
//判断是否含有协议为bundle的资源,没有则返回原值
rootDirResource = resolveRootDirResource(rootDirResource);
//vfs协议
if (rootDirResource.getURL().getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirResource, subPattern, getPathMatcher()));
}
//jar协议、zip协议、wsjar协议、vfszip协议
else if (isJarResource(rootDirResource)) {
//从jar包中找寻相应的所有class文件
result.addAll(doFindPathMatchingJarResources(rootDirResource, subPattern));
}
else {
//加载非jar、zip包的项目资源
result.addAll(doFindPathMatchingFileResources(rootDirResource, subPattern));
}
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved location pattern [" + locationPattern + "] to resources " + result);
}
return result.toArray(new Resource[result.size()]);
}

为了理解得更清楚,我们再抽取必要的代码进行分析,比如PathMatchingResourcePatternResolver#findAllClassPathResources()PathMatchingResourcePatternResolver#doFindPathMatchingFileResources()

  • PathMatchingResourcePatternResolver#findAllClassPathResources

    通过classloader来加载资源目录,代码如下
protected Resource[] findAllClassPathResources(String location) throws IOException {
String path = location;
//例如com/question/
if (path.startsWith("/")) {
path = path.substring(1);
}
//真实查找方法
Set<Resource> result = doFindAllClassPathResources(path);
return result.toArray(new Resource[result.size()]);
}

进而看PathMatchingResourcePatternResolver#doFindAllClassPathResources()

protected Set<Resource> doFindAllClassPathResources(String path) throws IOException {
Set<Resource> result = new LinkedHashSet<Resource>(16);
ClassLoader cl = getClassLoader();
//通过classloader来加载资源目录,这里也会去找寻classpath路径下的jar包或者zip包
Enumeration<URL> resourceUrls = (cl != null ? cl.getResources(path) : ClassLoader.getSystemResources(path));
while (resourceUrls.hasMoreElements()) {
URL url = resourceUrls.nextElement();
//对找到的路径保存为UrlResource对象放入set集合中
result.add(convertClassLoaderURL(url));
}
if ("".equals(path)) {
//加载jar协议的资源
addAllClassLoaderJarRoots(cl, result);
}
return result;
}

Note:一般而言找到的结果为一个,也就是file协议的项目工程资源目录,不建议查找的base-package含有jar包的资源目录,比如org.springframework

  • PathMatchingResourcePatternResolver#doFindPathMatchingFileResources()

    查找指定目录下的所有文件,这里特指class文件
protected Set<Resource> doFindPathMatchingFileResources(Resource rootDirResource, String subPattern)
throws IOException { File rootDir;
try {
//获取绝对路径对应的file
rootDir = rootDirResource.getFile().getAbsoluteFile();
}
catch (IOException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Cannot search for matching files underneath " + rootDirResource +
" because it does not correspond to a directory in the file system", ex);
}
//异常则返回空的集合
return Collections.emptySet();
}
return doFindMatchingFileSystemResources(rootDir, subPattern);
}

进而看真实的查找方法doFindMatchingFileSystemResources(),代码如下

protected Set<Resource> doFindMatchingFileSystemResources(File rootDir, String subPattern) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Looking for matching resources in directory tree [" + rootDir.getPath() + "]");
}
//真实的调用方法
Set<File> matchingFiles = retrieveMatchingFiles(rootDir, subPattern);
Set<Resource> result = new LinkedHashSet<Resource>(matchingFiles.size());
for (File file : matchingFiles) {
//对查找到的资源包装为FileSystemResource对象
result.add(new FileSystemResource(file));
}
return result;
}

继续观察真实加载文件资源的方法retriveMatchingFiles(),代码如下

protected Set<File> retrieveMatchingFiles(File rootDir, String pattern) throws IOException {
//根目录不存在?返回空集合
if (!rootDir.exists()) {
// Silently skip non-existing directories.
if (logger.isDebugEnabled()) {
logger.debug("Skipping [" + rootDir.getAbsolutePath() + "] because it does not exist");
}
return Collections.emptySet();
}
//不是目录?返回为空
if (!rootDir.isDirectory()) {
// Complain louder if it exists but is no directory.
if (logger.isWarnEnabled()) {
logger.warn("Skipping [" + rootDir.getAbsolutePath() + "] because it does not denote a directory");
}
return Collections.emptySet();
}
//不可读?返回为空
if (!rootDir.canRead()) {
if (logger.isWarnEnabled()) {
logger.warn("Cannot search for matching files underneath directory [" + rootDir.getAbsolutePath() +
"] because the application is not allowed to read the directory");
}
return Collections.emptySet();
}
//转换根目录全路径为标准的查找路径
String fullPattern = StringUtils.replace(rootDir.getAbsolutePath(), File.separator, "/");
if (!pattern.startsWith("/")) {
fullPattern += "/";
}
//查找类型为.class文件
fullPattern = fullPattern + StringUtils.replace(pattern, File.separator, "/");
Set<File> result = new LinkedHashSet<File>(8);
doRetrieveMatchingFiles(fullPattern, rootDir, result);
return result;
}

接着瞧doRetriveMathingFiles的重载方法,代码如下

protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<File> result) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("Searching directory [" + dir.getAbsolutePath() +
"] for files matching pattern [" + fullPattern + "]");
}
//从根目录开始罗列文件集合
File[] dirContents = dir.listFiles();
if (dirContents == null) {
//查找到没有了则直接返回
if (logger.isWarnEnabled()) {
logger.warn("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
}
return;
}
//遍历
for (File content : dirContents) {
//获取当前文件路径
String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
//查找到的子文件仍是目录且以根目录为开头
if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
if (!content.canRead()) {
if (logger.isDebugEnabled()) {
logger.debug("Skipping subdirectory [" + dir.getAbsolutePath() +
"] because the application is not allowed to read the directory");
}
}
else {
//递归调用查找所有的文件
doRetrieveMatchingFiles(fullPattern, content, result);
}
}
//查看当前文件路径是否满足**/*.class格式,满足则添加
if (getPathMatcher().match(fullPattern, currPath)) {
result.add(content);
}
}
}

小结

  1. classpath*:表示查找classpath路径下的所有符合条件的资源,包含jar、zip等资源;classpath:表示优先在项目的资源目录下查找,找不到才去jar、zip等资源中查找

  2. 该类可以帮助spring查找到符合ant-style格式的所有资源,所以富有借鉴意义。附:ant-style指的是类似*/?此类的匹配字符

Spring源码情操陶冶-PathMatchingResourcePatternResolver路径资源匹配溶解器的更多相关文章

  1. Spring源码情操陶冶-ComponentScanBeanDefinitionParser文件扫描解析器

    承接前文Spring源码情操陶冶-自定义节点的解析,本文讲述spring通过context:component-scan节点干了什么事 ComponentScanBeanDefinitionParse ...

  2. Spring源码情操陶冶-AbstractApplicationContext#obtainFreshBeanFactory

    前言-阅读源码有利于陶冶情操,本文承接前文Spring源码情操陶冶-AbstractApplicationContext 约束: 本文指定contextClass为默认的XmlWebApplicati ...

  3. Spring源码情操陶冶-ContextLoader

    前言-阅读源码有利于陶冶情操,本文承接前文Spring源码情操陶冶-ContextLoaderListener 静态代码块内容 ContextLoader在被主动调用的时候,会执行其的一个静态块,代码 ...

  4. Spring源码情操陶冶-DefaultBeanDefinitionDocumentReader#parseBeanDefinitions

    前言-阅读源码有利于陶冶情操,本文承接前文Spring源码情操陶冶-AbstractApplicationContext#obtainFreshBeanFactory 前文提到最关键的地方是解析bea ...

  5. Spring源码情操陶冶-PropertyPlaceholderBeanDefinitionParser注解配置解析器

    本文针对spring配置的context:property-placeholder作下简单的分析,承接前文Spring源码情操陶冶-自定义节点的解析 spring配置文件应用 <context: ...

  6. Spring源码情操陶冶-AbstractApplicationContext#finishBeanFactoryInitialization

    承接前文Spring源码情操陶冶-AbstractApplicationContext#registerListeners 约定web.xml配置的contextClass为默认值XmlWebAppl ...

  7. Spring源码情操陶冶-AbstractApplicationContext#invokeBeanFactoryPostProcessors

    阅读源码有利于陶冶情操,承接前文Spring源码情操陶冶-AbstractApplicationContext#postProcessBeanFactory 约定:web.xml中配置的context ...

  8. Spring源码情操陶冶-AbstractApplicationContext#prepareRefresh

    前言-阅读源码有利于陶冶情操,本文承接前文Spring源码情操陶冶-AbstractApplicationContext 约束: 本文指定contextClass为默认的XmlWebApplicati ...

  9. Spring源码情操陶冶-自定义节点的解析

    本文承接前文Spring源码情操陶冶-DefaultBeanDefinitionDocumentReader#parseBeanDefinitions,特开辟出一块新地来啃啃这块有意思的骨头 自定义节 ...

随机推荐

  1. UVA 11636-Hello World!(水题,猜结论)

    UVA11636-Hello World! Time limit: 1.000 seconds When you first made the computer to print the sentenc ...

  2. 状压dp初探

    写了几道状压...然后就一直在颓废... 2064: 分裂 http://www.lydsy.com/JudgeOnline/problem.php?id=2064 初始的为正,最后的为负,假设我们能 ...

  3. HDU1114Piggy-Bank(完全背包)

    Piggy-Bank Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total ...

  4. Cookie实现--用户上次访问时间

    用户上次访问时间  

  5. github设置添加SSH

    很多朋友在用github管理项目的时候,都是直接使用https url克隆到本地,当然也有有些人使用 SSH url 克隆到本地.然而,为什么绝大多数人会使用https url克隆呢? 这是因为,使用 ...

  6. PHP flock() 函数

    定义和用法 flock() 函数锁定或释放文件. 若成功,则返回 true.若失败,则返回 false. 语法 flock(file,lock,block) 参数 描述 file 必需.规定要锁定或释 ...

  7. nodejs+websocket实时聊天系统

    介绍下websocket: webSocket协议本质上是一个基于tcp的协议; 建立一个websocket连接,大体的过程: 1.客户端浏览器首先向服务器发起一个http请求,这个请求和平常的请求有 ...

  8. eclipse 按住ctrl 按钮没有反映

    以下是修改为XML Editior打开方法Window -> Preferences -> General -> Editors -> File Associations Fi ...

  9. Mysql 范围查询优化

    Range查询:用单独的Index的一个或多个index值来检索表的子集行数据,当然包含多个index. 1:一个index (单一部分)的range access 方法:(eg : 指的这种key ...

  10. Freemarker 入门示例

    初步学习freemarker ,先做一个简单的HelloWord程序! 新建一个WEB工程,下载(我使用的是freemarker-2.3.20)freemarker并导入freemarker.jar, ...