项目结构
1 | │ |
读取方式
1 | public class ResourceTest { |
总结规律如下:
Class.getResource()的资源获取如果以/开头, 则从根路径开始搜索资源Class.getResource()的资源获取如果不以/开头, 则从当前类所在的路径开始搜索资源ClassLoader.getResource()的资源获取不能以/开头, 统一从根路径开始搜索资源
源码分析
首先看一下Class类中的两个方法实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public InputStream getResourceAsStream(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResourceAsStream(name);
}
return cl.getResourceAsStream(name);
}
public java.net.URL getResource(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResource(name);
}
return cl.getResource(name);
}
可以看到, Class类先通过resoveName方法解析出资源文件路径, 然后委托ClassLoader去加载资源的, 首先看一下resolveName方法的实现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.test.ResourceTest->com/test/, 最后拼接上资源路径入参作为资源文件的路径
再看一下ClassLoader的两个方法实现
1 | public URL getResource(String name) { |
getResourceAsStream方法也是先调用同类的getResource方法, 所以重点看一下getResource方法的实现. 该方法首先使用了双亲委派机制来加载资源, 最终的父类ClassLoader使用getBootstrapClassPath去加载资源, 如果父类没加载到还会findResource去加载, 因此ClassLoader子类可以通过覆盖findResource方法来实现自定义的资源加载实现.
本示例中就是通过AppClassLoader的 findResource方法(实际是继承自URLClassLoader)加载到的资源, 该方法在URLClassLoader中实现如下1
2
3
4
5
6
7
8
9
10
11
12
13public URL findResource(final String name) {
/*
* The same restriction to finding classes applies to resources
*/
URL url = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
return ucp.findResource(name, true);
}
}, acc);
return url != null ? ucp.checkURL(url) : null;
}