14

我正在为需要在初始化期间加载配置文件的项目创建一个 JUnit 测试用例。

此配置文件位于项目中的 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()(我维护它,只是为了比较值)! !!

这里发生了什么?!

为什么直接调用方法不起作用,而在工作之间插入新的方法调用?

老实说,我真的很惊讶在这面前。

顺便说一句,我使用的是 JUnit 4.10 版。它可能会以某种方式篡改 getClassLoader 调用吗?

非常感谢,

卡尔斯

4

3 回答 3

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 的getResourceAsStream()方法实际上旨在用于相对路径(否则您将只使用 a FileInputStream)。

因此,当您使用 时this.getClass().getClassLoader().getResourceAsStream("/config/config.xml");,实际上是在开头带有斜杠的路径传递给它,但失败了。当你使用this.getClass().getResourceAsStream("/config/config.xml");它时,它会为你移除它。

于 2014-10-29T19:23:42.133 回答
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

您可以使用以下代码片段对此进行测试,因为两者都getResource使用getResourceAsStream了相同的搜索算法。

System.out.println(Test.class.getClassLoader().getResource("config/config.xml"));
System.out.println(Test.class.getResource("config/config.xml"));
于 2014-11-03T12:29:04.493 回答
1

我不确定这一点,但您可以尝试将资源文件夹放在 src/main 树中,而不是 src/test 树中。至少在某些配置中,eclipse 将“资源”文件从 src 复制到类,而不是从测试复制到类。值得一试...

于 2013-07-18T18:30:46.033 回答