最近在读取配置文件中的信息时,遇到了资源文件放置路径的问题。具体来讲就是当属性文件与类在同一包中时,使用Class.getResourceAsStream可以取得属性文件中的内容,而ClassLoader.getResourceAsStream却不能,当属性文件在类路径中时,情况正好相反。需要澄清的是两个方法的参数值为属性文件的名称,不包含路径信息。代码如下:
package test;
import java.io.IOException;
import java.io.InputStream;
import java.util.PropertyResourceBundle;
public class TestGetResourceAsStream {
public static void main(String[] args) throws IOException {
PropertyResourceBundle proResource;
InputStream inputStream;
//当属性文件与类在同一个包中时,Class.getResourceAsStream输出属性test的值,
//ClassLoader.getResourceAsStream找不到属性文件
//当属性文件位于类路径中时,而不在类的包中时,ClassLoader.getResourceAsStream输出属性test的值,
//Class.getResourceAsStream找不到属性文件
inputStream = TestGetResourceAsStream.class
.getResourceAsStream("test.properties");
if(inputStream != null){
proResource = new PropertyResourceBundle(inputStream);
System.out.println("Class.getResourceAsStream: "
+ proResource.getString("test"));
}else{
System.out.println("Class.getResourceAsStream can not find resource on classpath");
}
inputStream = TestGetResourceAsStream.class
.getClassLoader().getResourceAsStream("test.properties");
if(inputStream != null){
proResource = new PropertyResourceBundle(inputStream);
System.out.println("ClassLoader.getResourceAsStream:"
+ proResource.getString("test"));
}else{
System.out.println("ClassLoader.getResourceAsStream can not find resource on classpath");
}
}
}
当属性文件test.properties与类不在同一个包中时,即在包之外时,运行上述代码的输出为:
Class.getResourceAsStream can not find resource on classpath
ClassLoader.getResourceAsStream:Hello world!
当属性文件与类文件在同一包中时,运行结果为:
Class.getResourceAsStream:Hello world!
ClassLoader.getResourceAsStream can not find resource on classpath
什么原因导致的这种差别呢?开始对此问题百思不得其解,感觉无论属性文件位于包中还是其它-classpath指定的位置,两个类中的getResourceAsStream方法都应该可以找到资源文件,但结果却不尽然,此时只能查看方法的API文档了。
首先看看Class类的getResourceAsStream方法的官方文档,文档的说明为Javai平台将该方法委托给加载该类对象的ClassLoader,如果该对象是由启动程序的ClassLoader加载的,那么该方法委托给ClassLoader.getSystemResourceAsStream(java.lang.String)。既然是委托给ClassLoader的getResourceAsStream方法,为什么输出结果却不一致呢?在此不得不继续查看官方文档,根据官方文档的说明,在委托之前需要对参数进行处理,处理逻辑为:
- 如果参数为null,直接返回null
- 如果参数不以/开头,则将参数的修改为该类所属的包名加上参数名,并将.替换为/。例如参数为hello,该类所属的包为com.test,则处理后的参数为com/test/hello。
- 如果参数以/开头,返回/后面的部分。例如参数为/hello,处理后的参数为hello。
Class类中的resolveName(String name)方法用于实现上述逻辑,具体代码为:
private 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;
}
结合演示代码,两种情况下(属性文件在包中和不在包中)传入的参数都为test.properties。当属性文件在包中时,TestGetResourceAsStream.class.getResourceAsStream("test.properties")实际执行的是ClassLoader.getResourceAsStream("test/test.properties"),可以找到资源文件,而TestGetResourceAsStream.class.getClassLoader().getResourceAsStream("test.properties")却在当前路径中无法找到资源文件。这时可以通过-cp选项指定目录来解决:
D:\BigData\TestClassPath\bin>java -cp .;./test/ test.TestGetResourceAsStream
Class.getResourceAsStream:Hello world!
ClassLoader.getResourceAsStream:Hello world!
当属性文件不在包中时,TestGetResourceAsStream.class.getClassLoader().getResourceAsStream("test.properties")可以在当前路径下发现资源文件,而TestGetResourceAsStream.class.getResourceAsStream("test.properties")实际执行的还是ClassLoader.getResourceAsStream("test/test.properties"),但包中却不存在test.properties文件,所以得不到属性值。
通过上面的代码演示并结合官方文档和源代码,可以得出结论,如果需要加载资源文件,最好的方法是使用ClassLoader的getResourceAsStream方法,且资源文件最好与类文件不在同一包中或者不在同一文件夹下,这样可以在将类文件打包为jar文件的情况下便于修改资源文件而无需加压缩jar文件,此时只需确保资源文件位于classpath即可。