【问题标题】:Java NIO can't read files from JRT imageJava NIO 无法从 JRT 镜像中读取文件
【发布时间】:2019-01-11 05:20:47
【问题描述】:

当我们通过 jlink 创建 Java 运行时,它会获取所有 Java 类/资源并将它们放入 JRT 映像文件中:lib/modules

这是我使用的基本 Maven 项目资源结构:

src
  main
    resources
      dict
        xkcd_en

我只是想阅读xkcd_en 文本文件。如果我们查看 JRT 文件,这里是:

>> jimage list /path/to/lib/modules
...
Module: main
    dict/xkcd_en
...

另外我已经在module-info 中明确打开它,以防万一:

module main {
    opens dict;
    // ..rest code omitted
}

我可以读取文件的唯一方法是将其作为输入流获取:

作品:

public static InputStream getResourceAsStream(String resource) {
    return FileUtils.class.getResourceAsStream(resource);
}

System.out.println(new BufferedReader(
    new InputStreamReader(getResourceAsStream("/dict/xkcd_en")))
            .lines().collect(Collectors.joining("\n"))
);

不起作用:

但如果我试图获取文件 URI 并通过 Java NIO API 读取它,它就不起作用:

public static URL getResourceOrThrow(String resource) {
    URL url = FileUtils.class.getResource(resource);
    Objects.requireNonNull(url);
    return url;
}

1 - Java NIO 找不到文件。但它确实存在,否则getResource() 返回null

System.out.println(Paths.get(getResourceOrThrow("/dict/xkcd_en").toURI()));
// /main/dict/xkcd_en

Files.readAllLines(Paths.get(getResourceOrThrow("/dict/xkcd_en").toURI()));

Caused by: java.nio.file.NoSuchFileException: /main/dict/xkcd_en
        at java.base/jdk.internal.jrtfs.JrtFileSystem.checkNode(JrtFileSystem.java:494)
        at java.base/jdk.internal.jrtfs.JrtFileSystem.getFileContent(JrtFileSystem.java:253)
        at java.base/jdk.internal.jrtfs.JrtFileSystem.newInputStream(JrtFileSystem.java:342)
        at java.base/jdk.internal.jrtfs.JrtPath.newInputStream(JrtPath.java:631)
        at java.base/jdk.internal.jrtfs.JrtFileSystemProvider.newInputStream(JrtFileSystemProvider.java:322)

2 - 如果您直接使用 FileSystem,则相同:

FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
System.out.println(fs.getPath("main/dict/xkcd_en"));
// main/dict/xkcd_en

Files.readAllLines(fs.getPath("main/dict/xkcd_en")));

Caused by: java.nio.file.NoSuchFileException: /main/dict/xkcd_en
    at java.base/jdk.internal.jrtfs.JrtFileSystem.checkNode(JrtFileSystem.java:494)

3 - Java NIO 甚至不知道jrt:/ 方案是什么。

Files.readAllLines(Paths.get(getResourceOrThrow("/dict/xkcd_en").toExternalForm()));

Caused by: java.nio.file.InvalidPathException: Illegal char <:> at index 3: jrt:/main/dict/xkcd_en
    at java.base/sun.nio.fs.WindowsPathParser.normalize(WindowsPathParser.java:182)
    at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:153)
    at java.base/sun.nio.fs.WindowsPathParser.parse(WindowsPathParser.java:77)
    at java.base/sun.nio.fs.WindowsPath.parse(WindowsPath.java:92)
    at java.base/sun.nio.fs.WindowsFileSystem.getPath(WindowsFileSystem.java:229)
    at java.base/java.nio.file.Path.of(Path.java:147)
    at java.base/java.nio.file.Paths.get(Paths.java:69)

这里是 JRT FS 的specs

根据 RFC 3986,jrt URL 是一个分层 URI,其语法为

jrt:/[$MODULE[/$PATH]]

其中 $MODULE 是可选的模块名称,而 $PATH(如果存在)是 该模块中特定类或资源文件的路径。这 jrt URL 的含义取决于它的结构:

  • jrt:/$MODULE/$PATH 引用给定 $MODULE 中名为 $PATH 的特定类或资源文件。
  • jrt:/$MODULE 引用模块 $MODULE 中的所有类和资源文件。
  • jrt:/ 指的是存储在当前运行时映像中的类和资源文件的整个集合。

所以获得的路径对我来说看起来不错。我哪里错了?

【问题讨论】:

  • jrt 文件系统提供程序中存在一个错误,请参阅:bugs.openjdk.java.net/browse/JDK-8216553 您可以通过在文件路径前加上“/modules”来解决它。还要指出您的示例使用 toExternalForm 调用 Paths.get(String) ,它用于将路径字符串转换为平台文件系统上的文件,而不是 jrtfs。
  • @AlanBateman 我想,一定有人已经报告了这个。我应该检查一下错误数据库。毕竟一年多前我给this answer添加了一个变通方法,所以我想,我知道这个错误的时间更长。
  • @AlanBateman 仅仅是从URIPath 的转换是错误还是modules 目录应该对jrtfs 的用户“透明”?
  • 是 URL -> jrt 路径转换有问题。 modules 目录对于 jrtfs 的用户来说很重要。

标签: java nio java-11 java-module


【解决方案1】:

JRT 文件系统

您引用的 JEP 部分专门处理 URL。如果您进一步阅读,您会发现它在哪里讨论了 JRT 文件系统:

jrt URL 方案的内置 NIO FileSystem 提供程序确保开发工具可以通过加载由 URL @987654329 命名的 FileSystem 来枚举和读取运行时映像中的类和资源文件@,如下:

FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
byte[] jlo = Files.readAllBytes(fs.getPath("modules", "java.base",
                                          "java/lang/Object.class"));

顶级modules 目录 [强调添加] 此文件系统中包含图像中每个模块的一个子目录。 顶级packages 目录 [强调添加] 包含图像中每个包的一个子目录,该子目录包含指向该模块的子目录的符号链接定义该包。

如您所见,JRT 文件系统在根目录下有两个目录:modulespackages。这些是作为JDK-8066492 的一部分添加的,其目的由该问题描述。所以问题不在于 NIO API 无法读取 JRT 映像中的资源。问题是:

/main/dict/xkcd_en

真的不存在。资源实际位于:

/modules/main/dict/xkcd_en

JRT 网址

JRT URL 采用以下三种形式之一(均在您在问题中引用的 JEP 部分中提及):

  1. jrt:/$MODULE/$PATH
  2. jrt:/$MODULE
  3. jrt:/

第一种形式是用于访问 JRT 映像中的特定资源和我们关心的资源。如您所见,该 URL 不包含上述顶级目录。您可以将 URL 视为始终相对于 modules 目录。


JRT 文件系统提供程序错误

也就是说,作为@Alan Batemanpointed out,您遇到了一个错误。当您有一个 JRT URL 并尝试将其转换为 Path 时,您应该得到一个指向现有文件的 Path。问题是这种转换没有考虑modules 目录。

JDK-8224946 在 Java 13 中修复了此错误。

【讨论】:

  • 感谢您的精彩解释。我认为作为 API 用户,我不应该关心文件系统如何在内部存储其数据。如果 URL 解析成功,则必须可以使用它来访问相应的资源。
  • 根据 Alan Bateman 在his comment 中链接的问题,看来这种行为至少有一部分是错误。
猜你喜欢
  • 2011-03-21
  • 2018-12-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-06
相关资源
最近更新 更多