Java:getClass().getResource().toURI() 对比 getClass().getResourceAsStream()

Java : getClass().getResource().toURI() Vs getClass().getResourceAsStream()

我有一个 java 多模块 sbt 项目,其中一些包含一个 resources 文件夹。

module-1
  resources
    template.xls
module-2
  resources
    other.xls

打包后得到:

lib/module-1.jar
lib/module-2.jar

我 运行 我的程序与任何其他 java 应用程序一样:java -cp "lib/*" MainClass

我的问题是从 module-2.jar 访问 template.xls

起初,我尝试了以下几行来获取我的模板:

URI template = getClass().getResource("/template.xls").toURI();
Files.newInputStream(Paths.get(template), StandardOpenOption.READ);

开发模式下它可以工作。但不在服务器上(部署后),它找不到资源。

java.nio.file.FileSystemNotFoundException: null [jar:file:/.../lib/module-1.jar!/template.xls]

经过一些研究,我修改了我的访问代码,如下所示,以使其在两种模式(开发和部署)下工作:

InputStream templateIS = getClass().getResourceAsStream("/template.xls");

我不明白为什么!

这两种方法有什么区别?

您的第一个方案不适用,因为 template.xls 打包在 一个 jar 文件中。它不是文件系统上的文件(而在打包为文件之前它可能在您的开发环境中)。因此 Files API 找不到它。

Class.getResourceAsStream() 使用 class 加载机制,class(显然)是从 .jar 文件加载的。

来自 docs,

The method getResource() returns a URL for the resource. The URL (and its representation) is specific to the implementation and the JVM (that is, the URL obtained in one runtime instance may not work in another). Its protocol is usually specific to the ClassLoader loading the resource. If the resource does not exist or is not visible due to security considerations, the methods return null.

If the client code wants to read the contents of the resource as an InputStream, it can apply the openStream() method on the URL. This is common enough to justify adding getResourceAsStream() to Class and ClassLoader. getResourceAsStream() the same as calling getResource().openStream(), except that getResourceAsStream() catches IO exceptions returns a null InputStream.

因此,getResourceAsStream() 与调用 getResource().openStream() 相同,除了 getResourceAsStream() 捕获 IO 异常 returns 一个空 InputStream。

Files.newInputStream,顾名思义,可以打开文件。它无法打开其他任何东西。仅用于文件。

InputStream 的概念要抽象得多。当你打开一个文件进行阅读时,你会得到一个输入流,是的。但是您还可以获得许多其他内容的输入流:从网络连接中读取;读取 zip 文件的解压内容。读取解密操作的输出。这个名字真的说明了一切:它是输入,它是数据流。这不仅适用于 'file on a filesystem'.

Paths.get(template) 产生一个路径对象,代表文件系统上的一个文件。如果 template 派生自 URI,则此 不起作用 除非您拥有的 URI 恰好是文件对象的 URI;大多数 URI 不是文件对象。

总而言之,在您的第一个示例中,您在 class 路径上找到一个资源(可以是文件,但不一定是。例如,它们可以是jar 文件),然后询问其 URI,将其提供给 Paths API 以将其转换为 Path 对象,然后要求 Files API 将其转换为 InputStream,这仅在URI 代表一个文件。

在第二个片段中,您只需请求 classloader 系统为您获取输入流。它知道该怎么做(毕竟,java 必须加载那些 class 文件!)。如果您要求的资源恰好由一个文件表示,那么它在内部将做与您的第一个代码段大致相同的事情:使用文件 API 打开文件进行阅读。但如果它是其他任何东西,它也知道如何做到这一点——它也知道如何通过网络获取资源,从内部 jar 文件,即时生成——class 加载的概念(这就是class.getResource让你访问)被抽象掉。

注意:你用错了。正确的做法是ClassYouAreWritingIn.class.getResourceClassYouAreWritingIn.class.getResourceAsStreamgetClass().getResource 不正确;在 subclassing 时会中断,而正确的形式不会。