当我们通过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

jrt URL是根据RFC 3986使用语法的分层URI
jrt:/ [$$ MODULE [/ $ PATH]]
其中$ MODULE是可选的模块名称,$ PATH(如果存在)是
该模块中特定类或资源文件的路径。的
jrt URL的含义取决于其结构:
  • jrt:/ $ MODULE / $ PATH指给定$ MODULE中名为$ PATH的特定类或资源文件。
  • jrt:/ $ MODULE引用$ MODULE模块中的所有类和资源文件。
  • jrt:/引用存储在当前运行时映像中的类和资源文件的整个集合。

  • 因此,获得的路径对我来说不错。我哪里错了?

    最佳答案

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

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

    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部分中都提到了这种形式):
  • jrt:/$MODULE/$PATH
  • jrt:/$MODULE
  • jrt:/

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

    JRT文件系统提供程序错误
    就是说,作为pointed out@Alan Bateman,您遇到了一个错误。当您拥有JRT URL并尝试将其转换为Path时,您应该会得到一个指向现有文件的Path。问题在于此转换未考虑modules目录。
    此错误已在Java 13中由JDK-8224946修复。

    07-24 09:32