Java NIO 无法从 JRT 映像中读取文件
Java NIO can't read files from JRT image
当我们通过 jlink 创建 Java 运行时时,它需要所有 java classes/resources 并将它们放入 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()
returns 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
A jrt URL is a hierarchical URI, per RFC 3986, with the syntax
jrt:/[$MODULE[/$PATH]]
where $MODULE is an optional module name and $PATH, if present, is the
path to a specific class or resource file within that module. The
meaning of a jrt URL depends upon its structure:
- jrt:/$MODULE/$PATH refers to the specific class or resource file named $PATH within the given $MODULE.
- jrt:/$MODULE refers to all of the class and resource files in the module $MODULE.
- jrt:/ refers to the entire collection of class and resource files stored in the current run-time image.
所以获得的路径对我来说看起来没问题。我哪里错了?
JRT 文件系统
您引用的 JEP 部分专门针对 URLs。如果您进一步阅读,您会发现其中讨论了 JRT 文件系统:
A built-in NIO FileSystem
provider for the jrt
URL scheme ensures that development tools can enumerate and read the class and resource files in a run-time image by loading the FileSystem
named by the URL jrt:/
, as follows:
FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
byte[] jlo = Files.readAllBytes(fs.getPath("modules", "java.base",
"java/lang/Object.class"));
The top-level modules
directory [emphasis added] in this filesystem contains one subdirectory for each module in the image. The top-level packages
directory [emphasis added] contains one subdirectory for each package in the image, and that subdirectory contains a symbolic link to the subdirectory for the module that defines that package.
如您所见,JRT 文件系统在根目录下有两个目录:modules
和packages
。这些是作为 JDK-8066492 的一部分添加的,它们的目的在那个问题中有描述。所以问题不在于 NIO API 无法读取 JRT 映像中的资源。问题是:
/main/dict/xkcd_en
真的不存在。资源实际位于:
/modules/main/dict/xkcd_en
JRT URLs
JRT URL 采用三种形式之一(所有形式都在您在问题中引用的 JEP 部分中提到):
jrt:/$MODULE/$PATH
jrt:/$MODULE
jrt:/
第一种形式是访问JRT镜像中我们关心的特定资源。如您所见,URL 不包括上面提到的顶级目录。您可以将 URL 视为始终相对于 modules
目录。
JRT 文件系统提供程序错误
也就是说,作为 by @Alan Bateman,您遇到了一个错误。当您有一个 JRT URL 并尝试将其转换为 Path
时,您 应该 得到一个指向现有文件的 Path
。问题是这种转换没有考虑 modules
目录。
此错误已在 Java 13 中由 JDK-8224946 修复。
当我们通过 jlink 创建 Java 运行时时,它需要所有 java classes/resources 并将它们放入 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()
returns 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
A jrt URL is a hierarchical URI, per RFC 3986, with the syntax
jrt:/[$MODULE[/$PATH]]
where $MODULE is an optional module name and $PATH, if present, is the path to a specific class or resource file within that module. The meaning of a jrt URL depends upon its structure:
- jrt:/$MODULE/$PATH refers to the specific class or resource file named $PATH within the given $MODULE.
- jrt:/$MODULE refers to all of the class and resource files in the module $MODULE.
- jrt:/ refers to the entire collection of class and resource files stored in the current run-time image.
所以获得的路径对我来说看起来没问题。我哪里错了?
JRT 文件系统
您引用的 JEP 部分专门针对 URLs。如果您进一步阅读,您会发现其中讨论了 JRT 文件系统:
A built-in NIO
FileSystem
provider for thejrt
URL scheme ensures that development tools can enumerate and read the class and resource files in a run-time image by loading theFileSystem
named by the URLjrt:/
, as follows:FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/")); byte[] jlo = Files.readAllBytes(fs.getPath("modules", "java.base", "java/lang/Object.class"));
The top-level
modules
directory [emphasis added] in this filesystem contains one subdirectory for each module in the image. The top-levelpackages
directory [emphasis added] contains one subdirectory for each package in the image, and that subdirectory contains a symbolic link to the subdirectory for the module that defines that package.
如您所见,JRT 文件系统在根目录下有两个目录:modules
和packages
。这些是作为 JDK-8066492 的一部分添加的,它们的目的在那个问题中有描述。所以问题不在于 NIO API 无法读取 JRT 映像中的资源。问题是:
/main/dict/xkcd_en
真的不存在。资源实际位于:
/modules/main/dict/xkcd_en
JRT URLs
JRT URL 采用三种形式之一(所有形式都在您在问题中引用的 JEP 部分中提到):
jrt:/$MODULE/$PATH
jrt:/$MODULE
jrt:/
第一种形式是访问JRT镜像中我们关心的特定资源。如您所见,URL 不包括上面提到的顶级目录。您可以将 URL 视为始终相对于 modules
目录。
JRT 文件系统提供程序错误
也就是说,作为 Path
时,您 应该 得到一个指向现有文件的 Path
。问题是这种转换没有考虑 modules
目录。
此错误已在 Java 13 中由 JDK-8224946 修复。