getResourceAsStream from JUnit

2022-09-04 02:01:43

我正在为一个需要在初始化期间加载配置文件的项目创建一个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()(我维护它,只是为了比较值)!!!

这是怎么回事?!

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

老实说,面对这一点,我真的很惊讶。

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

非常感谢,

卡勒斯


答案 1

回复您的问题

如果我调试它,我可以清楚地看到这个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()

通常,它将尝试获取相对于它没有斜杠的资源,如果开始时确实有斜杠,则将其传递给classLoader。this

classLoader 的方法实际上旨在用于相对路径(否则您只会使用 )。getResourceAsStream()FileInputStream

因此,当您使用 时,您实际上在开始时用斜杠将其传递路径,但失败了。当你使用它时,它足以为你删除它。this.getClass().getClassLoader().getResourceAsStream("/config/config.xml");this.getClass().getResourceAsStream("/config/config.xml");


答案 2

getResourceAsStream来自对象的函数不会删除在搜索字符串前面附加的斜杠,因为搜索将相对于类路径完成。即搜索用于加载类的搜索路径的资源。ClassLoader

例如,假设您的类 位于 下,由系统类装入器(即默认类装入器)装入,并且您的类路径必须指向 才能装入类。搜索将在此路径上完成。yourpackage/Test.class/a/b/c/d/yourpackage/Test.class/a/b/c/d

getResourceAsStreamClass Object 中的 function 确实会删除搜索字符串前面的斜杠,因为搜索将相对于它所在的类完成。即搜索从中加载类的资源。

例如,如果从中加载,则资源路径将为yourpackage/Test.class/a/b/c/d/yourpackage/Test.class/a/b/c/d/yourpackage/config/config.xml

您可以使用以下代码截图对此进行测试,因为两者都用于遵循相同的搜索算法。getResourcegetResourceAsStream

System.out.println(Test.class.getClassLoader().getResource("config/config.xml"));
System.out.println(Test.class.getResource("config/config.xml"));

推荐