JVM加载配置资源文件有两种方式:
1、ClassLoader#getResource
2、Class#getResource
两者之间的区别:
ClassLoader并不关心当前类的包名路径,它永远以classpath为基点来定位资源。需要注意的是在用ClassLoader加载资源时,路径不要以”/“开头,所有以”/“开头的路径都返回null;
Class.getResource如果资源名是绝对路径(以”/“开头),那么会以classpath为基准路径去加载资源,如果不以”/“开头,那么以这个类的Class文件所在的路径为基准路径去加载资源
从源代码层次分析一下,这个结论对不对?
ClassLoader#getResource
1 | public InputStream getResourceAsStream(String name) { |
1 | public URL getResource(String name) { |
相对路径
1 | //classloader从根节点开始查找 |
这样子是正常获取到资源
绝对路径
1 | //以目录作对比,这样写,应该也没问题,但为什么返回是null呢? |
很多资料的结论classloader加载资源时,不要以 / 开头,以 / 开头都会返回null,是正确的。
从debug中可以看出来为什么以/开头,获取不到对应的资源。
主要还是对根节点的理解不一样:
classcloader以根节点去查找,是以当前的classpath为起点;
而以 / 开头,就变成类似root下了,自然查找不到
相对路径
1 | final URL resource2 = Thread.currentThread().getContextClassLoader().getResource("./root.properties"); |
这样是可以成功查找。
Class#getResouce
1 | public InputStream getResourceAsStream(String name) { |
看着Class#getResource,多加了一步resolveName,其实还是使用了Classloader#getResource方法
其中resolveName()
name不以’/‘开头时,默认是从此类所在的包下取资源;
name以’/‘开头时,则会substring(1),踢掉/,绝对路径变相对数据再从ClassPath根下获取资源1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20private String resolveName(String name) {
if (name == null) {
return name;
}
if (!name.startsWith("/")) {
Class<?> c = this;
while (c.isArray()) {
c = c.getComponentType();
}
String baseName = c.getName();
int index = baseName.lastIndexOf('.');
if (index != -1) {
name = baseName.substring(0, index).replace('.', '/')
+"/"+name;
}
} else {
name = name.substring(1);
}
return name;
}
在与com.zhuxingsheng.lang.GetResourceTest同目录下面创建package.properties
直接写文件名
1 |
|
根据debug信息,可以看出会从当前类目录去查找
绝对路径
1 | final URL resource1 = getClass().getResource("/com/zhuxingsheng/lang/package.properties"); |
从resolveName方法,可知,写了绝对路径,会被substring(1),也就是手动拼接完事包路径,走了resolveName的第一个不以 / 开头的分支路径。
也就是classloader#getResource不要写绝对路径。
完整相对路径
1 |
|
从debug中可以看出,就是把完整的路径拼接了两次,路径变成了com/zhuxingsheng/lang/com/zhuxingsheng/lang/package.properties
结论
经过源代码的debug,上文的结论是正确的。
ClassLoader并不关心当前类的包名路径,它永远以classpath为基点来定位资源。需要注意的是在用ClassLoader加载资源时,路径不要以”/“开头,所有以”/“开头的路径都返回null;
Class.getResource如果资源名是绝对路径(以”/“开头),那么会以classpath为基准路径去加载资源,如果不以”/“开头,那么以这个类的Class文件所在的路径为基准路径去加载资源
但在springboot中,自定义了classloader,打破了上述规则。下篇再看springboot的加载机制。