本文简单的分析下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. [bzoj1587] [Usaco2009 Mar]Cleaning Up 打扫卫生

    首先(看题解)可得...分成的任意一段中的不同颜色个数都<=根号n...不然的话直接分成n段会更优= = 然后就好做多了.. 先预处理出对于每头牛i,和它颜色相同的前一头和后一头牛的位置. 假设 ...

  2. vuex的使用及持久化state的方式

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化. 当我们接触vuex的时候,这是我们最先看到 ...

  3. mysql中配置ssl_key、ssl-cert、ssl-ca的路径及建立ssl连接

    1.创建 CA 私钥和 CA 证书 (1)下载并安装openssl,将bin目录配置到环境变量: (2)设置openssl.cfg路径(若不设置会报错,找不到openssl配置文件) \bin\ope ...

  4. 使用 SVG 和 JS 创建一个由星形变心形的动画

    序言:首先,这是一篇学习 SVG 及 JS 动画不可多得的优秀文章.我非常喜欢 Ana Tudor 写的教程.在她的教程中有大量使用 SVG 制作的图解以及实时交互 DEMO,可以说教程的所有细枝末节 ...

  5. js object 常用方法总结

    Object.assign(target,source1,source2,...) 该方法主要用于对象的合并,将源对象source的所有可枚举属性合并到目标对象target上,此方法只拷贝源对象的自身 ...

  6. HTML 5 video 视频标签全属性详解

    http://www.cnblogs.com/kiter/archive/2013/02/25/2932157.html 现在如果要在页面中使用video标签,需要考虑三种情况,支持Ogg Theor ...

  7. jQuery操作表格(table)的常用方法、技巧汇总

    摘录自:http://www.jb51.net/article/48943.htm 虽然现在DIV+CSS进行页的布局大行其道,但是很多地方使用table还是有很多优势,用table展示数据是比较方便 ...

  8. python中的Queue(队列)详解

    一.Queue简介 python中的队列分类可分为两种: 1.线程Queue,也就是普通的Queue 2.进程Queue,在多线程与多进程会介绍. Queue的种类: FIFO:  Queue.Que ...

  9. 写一个简单的配置文件和日志管理(shell)

    最近在做一个Linux系统方案的设计,写了一个之前升级服务程序的配置和日志管理. 共4个文件,服务端一个UpdateServer.conf配置文件和一个UpdateServer脚本,客户端一个Upda ...

  10. 优化order by 语句

    mysql 演示数据库:http://downloads.mysql.com/docs/sakila-db.zip mysql 中排序方式 有序索引顺序扫描直接返回有序数据 explain selec ...