Class.getResourceAsStream()
与 ClassLoader.getResourceAsStream()
两个方法都是加载类路径下资源的方法。类路径下文件的表示方法分为两种:绝对路径、相对路径,针对不同的路径,两个方法是否都支持?
准备工作
本文示例使用 Maven 进行构建,使用 .properties
在 resources
目录下添加资源文件 root.properties
,内容如下:
resource.path=/
在 resources
目录下添加子目录 demo
,然后在子目录下添加资源文件 current.properties
,内容如下:
resource.path=/demo
这样,根目录及子目录下分别创建了一个资源文件。
Class.getResourceAsStream()
在 demo
包下创建测试类。
public class ResourceDemo {
public static void main(String[] args) throws Exception {
loadResourceByClass("/root.properties", "使用绝对路径加载根目录下资源");
System.out.println("------------------------------------------");
loadResourceByClass("../root.properties", "使用相对路径加载根目录下资源");
System.out.println("------------------------------------------");
loadResourceByClass("/demo/current.properties", "使用绝对路径加载当前目录下资源");
System.out.println("------------------------------------------");
loadResourceByClass("current.properties", "使用相对路径加载当前目录下资源");
}
private static void loadResourceByClass(String url, String message) throws Exception {
try {
System.out.println(message);
InputStream resource = ResourceDemo.class.getResourceAsStream(url);
Properties properties = new Properties();
properties.load(resource);
properties.forEach((k, v) -> System.out.println(k + " => " + v));
} catch (Exception e) {
e.printStackTrace();
}
}
}
打印结果
使用绝对路径加载根目录下资源
resource.path => /
------------------------------------------
使用相对路径加载根目录下资源
resource.path => /
------------------------------------------
使用绝对路径加载当前目录下资源
resource.path => /demo
------------------------------------------
使用相对路径加载当前目录下资源
resource.path => /demo
总结
- 支持绝对路径加载:绝对路径以
/
开头,从classpath
根目录开始查找 - 支持相对路径加载:相对路径不以
/
开头,从当前类所在目录开始查找
ClassLoader.getResourceAsStream()
ClassLoader
类中有实例方法 getResourceAsStream()
以及静态方法 getSystemResourceAsStream()
都可以用于资源加载,后者使用 ClassLoader.getSystemClassLoader()
得到系统的 ClassLoader
然后再调用 getResourceAsStream()
。
使用上述四个路径再次测试一下 ClassLoader
的加载。
public class ResourceDemo {
public static void main(String[] args) throws Exception {
loadResourceByClassLoader("/root.properties", "使用绝对路径加载根目录下资源");
System.out.println("------------------------------------------");
loadResourceByClassLoader("../root.properties", "使用相对路径加载根目录下资源");
System.out.println("------------------------------------------");
loadResourceByClassLoader("/demo/current.properties", "使用绝对路径加载当前目录下资源");
System.out.println("------------------------------------------");
loadResourceByClassLoader("current.properties", "使用相对路径加载当前目录下资源");
}
private static void loadResourceByClassLoader(String url, String message) throws Exception {
try {
System.out.println(message);
InputStream resource = ClassLoader.getSystemResourceAsStream(url);
Properties properties = new Properties();
properties.load(resource);
properties.forEach((k, v) -> System.out.println(k + " => " + v));
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果全部都报 NullPointerException
,说明 Class.getResourceAsStream()
方法可以正常使用的路径对 ClassLoader
并不适用。ClassLoader.getSystemResourceAsStream()
方法支持的路径为:
- 不支持以
/
开头的路径 - 不以
/
开头的路径,也是从根目录开始查找
所以,要找到根目录下的 root.properties
以及 demo
目录下的 current.properties
两个文件,需要使用的路径分别为 root.properties
及 demo/current.properties
public class ResourceDemo {
public static void main(String[] args) throws Exception {
loadResourceByClassLoader("root.properties", "加载根目录下资源");
System.out.println("------------------------------------------");
loadResourceByClassLoader("demo/current.properties", "使用绝对路径加载当前目录下资源");
}
private static void loadResourceByClassLoader(String url, String message) throws Exception {
try {
System.out.println(message);
InputStream resource = ClassLoader.getSystemResourceAsStream(url);
Properties properties = new Properties();
properties.load(resource);
properties.forEach((k, v) -> System.out.println(k + " => " + v));
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果如下
加载根目录下资源
resource.path => /
------------------------------------------
加载当前目录下资源
resource.path => /demo
总结
- 不支持
/
开头的绝对路径 - 不以
/
开头的路径,也是从classpath
根目录开始查找
源码分析
其实 Class.getResourceAsStream()
方法最终也是使用的 ClassLoader
进行资源加载
public InputStream getResourceAsStream(String var1) {
var1 = this.resolveName(var1);
ClassLoader var2 = this.getClassLoader0();
return var2 == null ? ClassLoader.getSystemResourceAsStream(var1) : var2.getResourceAsStream(var1);
}
但是,Class.getResourceAsStream()
方法与 ClassLoader.getSystemResourceAsStream()
方法支持的路径格式却不一样,其实就是 resolveName
方法进行了路径转换。
private String resolveName(String var1) {
if (var1 == null) {
return var1;
} else {
if (!var1.startsWith("/")) {
// 相对路径,获取当前类路径,然后接上资源名
Class var2;
for(var2 = this; var2.isArray(); var2 = var2.getComponentType()) {
}
String var3 = var2.getName();
int var4 = var3.lastIndexOf(46);
if (var4 != -1) {
var1 = var3.substring(0, var4).replace('.', '/') + "/" + var1;
}
} else {
// 绝对路径,直接去掉开头的 `/`
var1 = var1.substring(1);
}
return var1;
}
}