在了解 classpath 获取资源之前,先了解 classLoader如何获取资源的
ClassLoader获取资源
为了说明怎么加载文件的,先举例一个具体的场景
现有 demo 项目,目录结构如下
d:
└─demo
└─src
└─com
└─aya
├─CustomMain.java
└─config.properties
这里获取 com/aya/config.properties 有以下两种方式:
- CustomMain.class.getResource(“config.properties”);
- Thread.currentThread().getContextClassLoader().getResource(“com/aya/config.properties”);
class 与 classLoader 获取资源的调用形式如下图:
Class加载资源
class 内部实际上的调用方式:
Thread.currentThread().getContextClassLoader().getResource( resolveName(clazz) +path)
resolveName(clazz) 会获得class类所在的目录名称
加载步骤如下:
- CustomMain.java 的目录 + 目标文件名 = com/aya/config.properties
- 用当前线程的 ClassLoader去加载目标资源
ClassLoader加载资源
- 尝试 BootStrap ClassLoader 加载资源
- 尝试 Extention ClassLoader 加载资源
- 尝试 当前的 ApplicationClassLoader
ClassLoader 根据双亲委派模型去加载资源,有父加载器则先去父加载器加载,如果没有加载成功,则在当前类加载器加载,以此类推
BootStrap ClassLoader
引导类加载器, 加载 jre/lib 目录下的jar包,JVM固定的几个jar包,把jar包丢进去也没用
Extention ClassLoader
扩展类加载器, 加载 jre/lib/ext 目录下的jar包,可以把jar包丢过去
ApplicationClassLoader
应用程序的类加载器,加载应用程序的根路径下的内容
注意: ClassLoader 加载的资源一定要有确定的名称,不支持通配符
ClassPathResource 加载资源
spring的 org.springframework.core.io.ClassPathResource 有三种构造 ,如下图
通常使用无参构造会使用当前线程的ClassLoader去加载(没有的话,就是用AppClassLoader加载)
在使用 org.springframework.core.io.ClassPathResource.getInputStream获取输入流的时候,会根据构造的内容选择加载方式
事实上 spring 的 ClassPathResource 就是使用的 ClassLoader.getResourceAsStream 获取的资源
classpath的星号问题
spring 可以使用 <import resource="classpath:spring-*.xml"/>
或者 <import resource="classpath*:spring-*.xml"/>
那么 classpath 与 classpath* 有什么区别呢?
import 标签解析的时候,会使用org.springframework.core.io.support.PathMatchingResourcePatternResolver.getResources
去加载
这里就不贴代码了,直接给出最终的搜索流程图
这里的资源表示:文件或者目录
资源 | 内容没有符号 | 内容有星号或者问号 | 加载jar包资源 |
---|---|---|---|
classpath: | 第一个匹配的资源 | 第一个匹配的目录下匹配的所有资源 | 会 |
classpath*: | 当前线程classLoader去加载所有资源 | 匹配所有的目录+匹配目录下的资源 | 会 |
classpath 与 classpath* 都会加载jar包的资源.
- 当存在通配符的时候,classpath 不一定先加载当前类路径的内容
- (根据双亲委派的加载机制,要是你放一个jar包在ExtClassLoader的目录下,如果classpath不加星号的话,所匹配的资源文件就跟当前工程没什么关系了)
- 匹配的内容如果有通配符(?,*),那么会先匹配确认的目录,然后再去目录下遍历每个文件的文件名和通配符是否匹配
tomcat匹配失败
<import resource="classpath*:spring-*.xml"/>
在 tomcat7 与 tomcat8 会有不同的效果, tomcat7 无法加载jar包的文件, tomcat8 却可以。
tomcat7默认使用的是 org.apache.catalina.loader.WebappClassLoader,tomcat8 默认使用的是 org.apache.catalina.loader.ParallelWebappClassLoader
可以在 tomcat 的 conf/context.xml 下加入Loader标签,强制修改应用的classLoader,事实上并没有用。
这两个类无论如何都会去加载jar包的内容,只是处理的方式不同而已
tomcat7
//WebappClassLoaderBase.getResources 下 (findResource方法实现内容)
// Looking at the JAR files
synchronized (jarFiles) {
if (openJARs()) {
for (i = 0; i < jarFilesLength; i++) {
JarEntry jarEntry = jarFiles[i].getJarEntry(name);//找不到文件或者文件夹就会返回null
if (jarEntry != null) {
try {
String jarFakeUrl = getURI(jarRealFiles[i]).toString();
result.add(UriUtil.buildJarUrl(jarFakeUrl, name));
} catch (MalformedURLException e) {
// Ignore
}
}
}
}
}
tomcat8
// 会返回jar包的根路径
WebResource[] webResources = resources.getClassLoaderResources(path);
for (WebResource webResource : webResources) {
if (webResource.exists()) {
result.add(webResource.getURL());
}
}
在tomcat7下要想解决星号匹配的问题,加入目录就可以了
<import resource="classpath*:dir/spring-*.xml"/>