我正在为需要在初始化期间加载配置文件的项目创建一个JUnit TestCase。
此配置文件位于src / main / resources / config文件夹中的项目内,并且在构建maven期间将其放入JAR内的/ config文件夹中。
初始化类,使用以下语句从那里读取文件:
ClassLoader classloader = this.getClass().getClassLoader();
BufferedReader xmlSource = new BufferedReader(new InputStreamReader(classLoader.getResourceAsStream("/config/config.xml")));
我遇到的问题是,当我将这个jar部署并执行到应用程序服务器时,它按预期工作,但是,每当我在Eclipse中的JUnit TestCase中运行它时,getResrouceAsStream方法都返回null。
考虑到该类是my.package.MyClassTest.java,并且它位于src / test / java / my / package / MyClassTest.java中,我已经尝试将config.xml文件的副本放入以下文件夹没有成功:
- src/test/resources/config
- src/test/resources/my/package/config
- src/test/java/my/package/config
我知道在StackOverflow中已经多次询问过类似的问题,但我发现的所有响应都指的是改变文件的加载方式,虽然更改代码可能是一个选项,但我更愿意找到该文件的正确位置,所以我不需要修改已在生产环境中工作的东西。
那么,我应该在哪里放置这个文件以便能够在我的JUnit测试中使用它?
更新
我只是在代码中进行了一些小改动,想出了解决方案: 我没有使用ClassLoader来获取资源,而是直接使用了类:
Class clazz = this.getClass();
BufferedReader xmlSource = new BufferedReader(new InputStreamReader(clazz.getResourceAsStream("/config/config.xml")));
它从src / test / resources / config / config.xml成功读取文件。
然而,这里有一些非常奇怪的东西: Class.getResourceAsStream方法是:
public InputStream getResourceAsStream(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResourceAsStream(name);
}
return cl.getResourceAsStream(name);
}
如果我调试它,我可以清楚地看到这个 getClassLoader0()返回与前一个调用完全相同的对象(相同的id), this.getClass()。getResourceAsStream( )(我维护,只是为了比较值)!!!
这里发生了什么?!
为什么在工作之间插入新方法调用时,直接调用方法不起作用?
老实说,我对此感到非常惊讶。BTW,我使用的是JUnit 4.10版。可能它以某种方式篡改了getClassLoader调用吗?
非常感谢,
卡尔斯
答案 0 :(得分:17)
回复你的问题
如果我调试它,我可以清楚地看到这个 getClassLoader0()返回与前一个调用完全相同的对象(相同的id), this.getClass()。getResourceAsStream( )(我维护,只是为了比较值)!!!
这里发生了什么?!
为什么在工作之间插入新方法调用时,直接调用方法不起作用?
调用
之间的区别this.getClass().getClassLoader().getResourceAsStream("/config/config.xml");
并致电
this.getClass().getResourceAsStream("/config/config.xml");
位于Class
显示的确切来源:
public InputStream getResourceAsStream(String name) {
name = resolveName(name);
ClassLoader cl = getClassLoader0();
if (cl==null) {
// A system class.
return ClassLoader.getSystemResourceAsStream(name);
}
return cl.getResourceAsStream(name);
}
但问题不在于getClassLoader0()
返回的问题。它在两种情况下都返回相同的内容。差异实际上在resolveName(name)
。这是Class
类中的私有方法。
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;
}
所以你看,在实际调用classLoader的getResourceAsStream()
之前,实际上从路径中删除了起始斜杠。
一般情况下,它会尝试在没有斜杠的情况下获取相对于this
的资源,如果它在开头有斜杠,则将其传递给classLoader。
classLoader的getResourceAsStream()
方法实际上用于相对路径(否则你只需使用FileInputStream
)。
所以当你使用this.getClass().getClassLoader().getResourceAsStream("/config/config.xml");
时,你实际上是在路径中用斜杠传递它,但失败了。当您使用this.getClass().getResourceAsStream("/config/config.xml");
时,它很适合为您删除它。
答案 1 :(得分:2)
getResourceAsStream
对象的 ClassLoader
函数不会删除搜索字符串前面的斜杠,因为搜索将相对于类路径进行。即搜索用于加载类的搜索路径的资源。
例如,假设您的类yourpackage/Test.class
位于/a/b/c/d/yourpackage/Test.class
下,由系统类加载器(即默认类加载器)加载,并且您的类路径必须指向/a/b/c/d
才能加载类。搜索将在此路径上完成。
getResourceAsStream
函数会删除搜索字符串前面的斜杠,因为搜索将相对于它所在的类进行。即搜索正在加载类的资源。
例如,如果从yourpackage/Test.class
加载/a/b/c/d/yourpackage/Test.class
,那么资源路径将为/a/b/c/d/yourpackage/config/config.xml
您可以使用以下代码snipet对此进行测试,因为getResource
和getResourceAsStream
过去都使用相同的搜索算法。
System.out.println(Test.class.getClassLoader().getResource("config/config.xml"));
System.out.println(Test.class.getResource("config/config.xml"));
答案 2 :(得分:1)
我不确定这一点,但您可以尝试将您的资源文件夹放在src / main树而不是src / test树中。在某些配置中,至少eclipse将“资源”文件从src复制到类,而不是从测试复制到类。这值得一试......