Class.getResource() 和 ClassLoader.getResource() 有啥区别?

Posted

技术标签:

【中文标题】Class.getResource() 和 ClassLoader.getResource() 有啥区别?【英文标题】:What is the difference between Class.getResource() and ClassLoader.getResource()?Class.getResource() 和 ClassLoader.getResource() 有什么区别? 【发布时间】:2011-09-30 07:54:22 【问题描述】:

不知道Class.getResource()ClassLoader.getResource()有什么区别?

edit:我特别想知道文件/目录级别是否涉及任何缓存。如“目录列表是否缓存在 Class 版本中?”

AFAIK 以下应该基本上做同样的事情,但他们不是:

getClass().getResource() 
getClass().getClassLoader().getResource()

我在摆弄一些报告生成代码时发现了这一点,该代码从该目录中的现有文件在WEB-INF/classes/ 中创建一个新文件。使用 Class 中的方法时,我可以使用 getClass().getResource() 找到部署时存在的文件,但是在尝试获取新创建的文件时,我收到了一个空对象。浏览目录清楚地表明新文件在那里。文件名前面带有正斜杠,如“/myFile.txt”。

另一方面,getResource()ClassLoader 版本确实找到了生成的文件。从这个经验来看,似乎对目录列表进行了某种缓存。我说得对吗?如果是的话,这是在哪里记录的?

来自API docsClass.getResource()

找到资源 有一个给定的名字。规则为 搜索与 a 相关的资源 给定的类由 定义类的类加载器。 这个方法委托给这个对象的 类加载器。如果这个对象是 由引导类加载器加载, 该方法委托给 ClassLoader.getSystemResource(java.lang.String)。

对我来说,这读作“Class.getResource 真的在调用它自己的类加载器的 getResource()”。这与getClass().getClassLoader().getResource() 相同。但显然不是。有人可以为我提供一些关于这件事的启示吗?

【问题讨论】:

【参考方案1】:

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");

【讨论】:

带有清晰示例的好答案。尽管该帖子实际上是为了获得两个问题的答案,但我现在看到第二个问题有点隐藏。非常不确定我应该如何/是否应该更新帖子以反映这一点,但我想知道第二个是这个(下一条评论): Class.getResource() 版本中是否存在某种缓存?是什么让我相信这是一些 jasper 报告的生成:我们使用 getClass().getResource("/aDocument.jrxml") 来获取 jasper xml 文件。然后在 same 目录中生成一个二进制 jasper 文件。 getClass().getResource("/aDocument.jasper") 无法找到它,尽管它可以清楚地找到同一级别的文档(输入文件)。这是 ClassLoader.getResource() 证明有用的地方,因为它似乎不使用目录列表的缓存。但我找不到这方面的文档。 @oligofren: 嗯...我不会期望 Class.getResource() 在那里做任何缓存... @JonSkeet 为什么this.getClass().getClassLoader().getResource("/"); 返回null?它不应与this.getClass().getClassLoader().getResource("."); 相同 @UnKnown:我想你应该问一个关于这个的新问题。【参考方案2】:

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

为了调试这样的问题,我打印了 URL:

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

【讨论】:

我认为“classloader root”会比“classpath root”更准确——只是为了挑剔。 如果文件名前面有“/”,两者都可以搜索“绝对路径” 有趣...我遇到了 getClass().getResource("/someAbsPath") 返回类型为 /path/to/mylib.jar!/someAbsPath 和 getClass() 的 URL .getClassLoafer().getResource("/someAbsPath") return null...所以“类加载器的根”似乎不是一个定义明确的概念... 见***.com/questions/13269556/… @PierreHenry: getClassLoader().getResource("/...") 总是返回 null - 类加载器不会从路径中删除前导 /,因此查找总是失败。只有getClass().getResource() 将起始/ 处理为相对于类路径的绝对路径。【参考方案3】:

必须在规格中查找:

Class.getResource(String resource)

ClassLoader.getResource(String resource)

类的 getResource() - 文档说明了区别:

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

【讨论】:

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

这里的所有这些答案,以及this question 中的答案,都表明加载绝对 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:我也通过here发布了这个

【讨论】:

这种行为似乎是 Tomcat 中的一个错误,已在版本 8 中修复。我在answer about this question 中添加了一段关于此的内容【参考方案5】:

回答是否有缓存的问题。

我通过运行一个独立的 Java 应用程序进一步研究了这一点,该应用程序使用 getResourceAsStream ClassLoader 方法从磁盘连续加载文件。我能够编辑文件,并且更改立即反映,即文件从磁盘重新加载而没有缓存。

但是: 我正在开发一个项目,其中包含多个相互依赖的 Maven 模块和 Web 项目。我使用 IntelliJ 作为我的 IDE 来编译和运行 Web 项目。

我注意到上述似乎不再适用,原因是我正在加载的文件现在被烘焙到一个 jar 中并部署到依赖的 web 项目中。我只是在尝试更改目标文件夹中的文件后才注意到这一点,但无济于事。这看起来好像正在进行缓存。

【讨论】:

我也使用过 Maven 和 IntelliJ,所以这是一个与我的环境最匹配的答案,并且对问题 #2 有合理的解释。【参考方案6】:

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

【讨论】:

【参考方案7】:

从 Java 9 开始,在模块路径上运行时,ClassLoader#getResource 存在一个陷阱。因此,我永远不会在新代码中使用ClassLoader#getResource

如果您的代码在命名模块中,并且您使用ClassLoader#getResource,那么即使资源在同一模块中,您的代码也可能无法检索资源这是非常令人惊讶的行为。

我自己也经历过,对Class#getResourceClassLoader#getResource 之间的这种差异感到非常惊讶。但是,这完全是根据 javadoc 指定的行为:

此外,除了资源名称以“.class”结尾的特殊情况外,此方法仅在无条件打开包时才会在命名模块的包中查找资源(即使调用方此方法与资源在同一模块中)。

Javadoc(强调我的)

【讨论】:

相关:What is an open module in Java 9 and how do I use it?【参考方案8】:

我尝试从我的一个包中的 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() 呢? 资源不是文件。它们可能不会从 JAR 或 WAR 文件中解压缩,如果不是,则不能使用 FileReaderFileInputStream 来访问它们。答案不正确。【参考方案9】:

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

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

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

File file = resource.getFile();

【讨论】:

这实际上与关于类加载器的讨论​​无关。没有人关心如何在这里加载文件,但有什么不同。

以上是关于Class.getResource() 和 ClassLoader.getResource() 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

关于Class.getResource和ClassLoader.getResource的路径问题

Class.getResource()和ClassLoader.getResource()异同及用法

关于Class.getResource和ClassLoader.getResource的路径问题

关于Class.getResource和ClassLoader.getResource的路径问题

可执行 jar 中 Class.getResource() 和 ClassLoader.getResource() 的奇怪行为

(转)关于Class.getResource和ClassLoader.getResource的路径问题