Class.getResource()和ClassLoader.getResource()有什么区别?

233

我想知道Class.getResource()ClassLoader.getResource()之间的区别是什么?

编辑:我特别想知道文件/目录级别上是否涉及缓存。就像“在Class版本中,目录列表是否被缓存?”

据我所知,以下代码应该基本相同,但它们并不相同:

getClass().getResource() 
getClass().getClassLoader().getResource()
当我调试一些报告生成代码时,我发现使用Class的方法可以通过getClass().getResource()找到已部署的文件,但是当尝试获取新创建的文件时,我收到了一个空对象。浏览目录清楚地显示新文件在那里。文件名以斜杠开头,例如 "/myFile.txt"。
ClassLoader版本的getResource()则可以找到生成的文件。从我的经验来看,似乎存在某种缓存目录列表的情况。我是正确的吗?如果是这样,那么这个地方在哪里有记录呢?
根据Class.getResource()API文档规则,搜索与给定类相关联的资源的规则由类的定义类加载器实现。
对我来说,这意味着“Class.getResource实际上调用其自己的类加载器的getResource()”。这应该与执行getClass().getClassLoader().getResource()相同。但显然不是这种情况。请问有人能够为我提供更多关于此问题的解释吗?
9个回答

303

Class.getResource可以接受一个“相对”的资源名称,该名称相对于类的包进行处理。或者,您可以使用前导斜杠指定“绝对”资源名称。类加载器资源路径始终被视为绝对路径。

因此,以下两种方式基本上是等效的:

foo.bar.Baz.class.getResource("xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("foo/bar/xyz.txt");

这些也是一样的(但它们与上面的不同):

foo.bar.Baz.class.getResource("/data/xyz.txt");
foo.bar.Baz.class.getClassLoader().getResource("data/xyz.txt");

好的答案,有清晰的例子。虽然这篇帖子实际上是为了得到两个问题的答案,但我现在看到第二个问题有点隐藏。我不太确定是否应该更新帖子来反映这一点,但我想知道第二个问题是什么(下一个评论): - oligofren
3
在 Class.getResource() 版本中是否发生了某种缓存?让我相信发生这种情况的原因是生成了一些 jasper 报告: 我们使用 getClass().getResource("/aDocument.jrxml") 来获取 jasper xml 文件。然后在相同的目录中产生一个二进制 jasper 文件。getClass().getResource("/aDocument.jasper") 无法找到它,尽管它可以清楚地找到相同级别(输入文件)的文档。这就是 ClassLoader.getResource() 证明有用的地方,因为它似乎不使用目录列表的缓存。但我找不到与此相关的文档。 - oligofren
3
嗯……我不会期望Class.getResource()在那里进行任何缓存…… - Jon Skeet
1
@JonSkeet 为什么 this.getClass().getClassLoader().getResource("/"); 返回 null?它不应该与 this.getClass().getClassLoader().getResource("."); 相同。 - Asif Mushtaq
@UnKnown:我认为你可能应该问一个新的关于那个问题的问题。 - Jon Skeet
@JonSkeet 带有前导斜杠的方法可以完美地从测试资源根目录中获取资源! - Gaurav

30

第一次调用相对于.class文件搜索,而后者相对于类路径根目录搜索。

为了调试此类问题,我会打印URL:

System.out.println( getClass().getResource(getClass().getSimpleName() + ".class") );

4
我认为“类加载器根目录”比“类路径根目录”更准确,只是有点挑剔。 - Jon Skeet
4
如果文件名以“/”开头,两者都可以搜索“绝对路径”。 - oligofren
3
有趣...我遇到了这样一种情况:getClass().getResource("/someAbsPath")返回的URL类型是/path/to/mylib.jar!/someAbsPath,而getClass().getClassLoader().getResource("/someAbsPath")则返回null。因此,“类加载器的根”似乎并不是一个非常明确定义的概念... - Pierre Henry
请参见https://dev59.com/LWYr5IYBdhLWcg3w8-kz。 - Pierre Henry
11
getClassLoader().getResource("/...") жҖ»жҳҜиҝ”еӣһ null - зұ»еҠ иҪҪеҷЁдёҚдјҡ移йҷӨи·Ҝеҫ„дёӯзҡ„ејҖеӨҙзҡ„ /пјҢжүҖд»ҘжҹҘжүҫе§Ӣз»ҲеӨұиҙҘгҖӮеҸӘжңү getClass().getResource() е°Ҷд»Ҙ / ејҖеӨҙзҡ„и·Ҝеҫ„и§ЈйҮҠдёәзӣёеҜ№дәҺзұ»и·Ҝеҫ„зҡ„з»қеҜ№и·Ҝеҫ„гҖӮ - Aaron Digulla

20

我不得不查阅规范文档:

Class的getResource()方法 - 文档中说明了它与ClassLoader.getResource()方法的区别:

此方法将调用委托给其类加载器,在对资源名称进行以下更改后:如果资源名称以“/”开头,则不会更改;否则,在将“.”转换为“/”后,包名称将被添加到资源名称之前。如果此对象由引导加载程序加载,则调用将委托给ClassLoader.getSystemResource。


2
你有关于它是否也缓存目录列表的信息吗?这是两种方法之间的主要区别,当首次查找输入文件,然后在同一目录中使用该文件创建文件时。Class版本没有找到它,ClassLoader版本找到了它(都使用“/file.txt”)。 - oligofren

12

这里所有的回答,以及这个问题中的答案都表明,加载绝对URL,比如"/foo/bar.properties",在class.getResourceAsStream(String)class.getClassLoader().getResourceAsStream(String)中会被视为相同。但是,在我的Tomcat配置/版本中(当前为7.0.40),情况并非如此。

MyClass.class.getResourceAsStream("/foo/bar.properties"); // works!  
MyClass.class.getClassLoader().getResourceAsStream("/foo/bar.properties"); // does NOT work!

抱歉,我没有令人满意的解释,但我猜想Tomcat可能会通过类加载器使用黑魔法来引起这种差异。过去我一直使用class.getResourceAsStream(String)而没有遇到任何问题。

PS:我也在这里发布了这个问题。


1
这种行为似乎是Tomcat中的一个错误,在8版本中已经修复。我在我的回答中添加了一段关于此的说明。 - LordOfThePigs

7
回答这个问题是否有任何缓存正在进行。我进一步调查了这一点,运行一个独立的Java应用程序,使用getResourceAsStream ClassLoader方法持续从磁盘加载文件。我能够编辑文件,更改立即反映出来,也就是说,文件重新从磁盘加载,不使用缓存。
然而: 我正在处理一个具有多个maven模块和相互依赖的Web项目的项目,我使用IntelliJ作为我的IDE来编译和运行Web项目。我注意到上面的情况似乎不再成立了,原因是我正在加载的文件现在被打包到一个jar中,并部署到依赖的Web项目中。在尝试更改目标文件夹中的文件后,我才注意到这一点是无效的。这使得它看起来像是有缓存正在进行。

我也使用Maven和IntelliJ,因此这是一个最接近我的环境并对问题#2有合理解释的答案。 - oligofren

4
自从Java 9之后,使用模块路径运行时,ClassLoader#getResource存在陷阱。因此,在新代码中我不会使用ClassLoader#getResource
如果你的代码在一个命名模块中,并且你使用ClassLoader#getResource,即使资源在同一个模块中,你的代码也可能无法检索到资源。这是非常令人惊讶的行为。
我自己也遇到了这个问题,并对Class#getResourceClassLoader#getResource之间的区别感到非常惊讶。然而,根据javadoc的完全规定行为:
另外,除非资源的名称以“.class”结尾,否则此方法将仅在已打开包的情况下(即使调用此方法的调用者与资源位于同一模块中),才能在命名模块的包中找到资源。 Javadoc(强调是我的)

谢谢,我花了好几个小时试图弄清楚到底发生了什么鬼东西。 - undefined

2

Class.getResources 方法会通过加载对象的类加载器来检索资源。而 ClassLoader.getResource 方法则会使用指定的类加载器来检索资源。


0

我尝试从一个包内的input1.txt文件中读取数据,与尝试读取它的类一起。

以下代码可以正常工作:

String fileName = FileTransferClient.class.getResource("input1.txt").getPath();

System.out.println(fileName);

BufferedReader bufferedTextIn = new BufferedReader(new FileReader(fileName));

最重要的部分是调用getPath(),如果您想要正确的字符串格式的路径名。请不要使用toString(),因为它会添加一些额外的格式化文本,这将完全搞乱文件名(您可以尝试并查看打印输出)。
花了2个小时来调试这个问题... :(

Class.getResourceAsStream() 怎么样? - Ilya Serbis
资源不是文件。它们可能没有从JAR或WAR文件中解压缩,如果没有,则无法使用FileReaderFileInputStream访问它们。答案不正确。 - user207421

0

另一种更有效的方法是只需使用@Value

@Value("classpath:sss.json")
private Resource resource;

然后你可以通过这种方式获取文件

File file = resource.getFile();


1
这与类加载器的讨论无关。在这里,没有人关心如何加载文件,而是关心什么使它与众不同。 - oligofren

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接