如何从另一个 JRT 读取 类?

How to read the classes from another JRT?

来自Java11、如何读取另一个runtime image的内容?

为了列出 Java 运行时映像的内容,JEP 220 建议采用以下解决方案:

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

此代码段有效,可让我在执行代码的 Java 安装的运行时映像中读取 java/lang/Object.class 的内容。

如何让它在另一个 Java 安装中读取 java/lang/Object.class 的内容,给定它的 java 主页?

我已阅读 ,其中解释了如何从 Java 8 运行时读取 Java 运行时图像的内容。不幸的是,这不适用于较新的 Java 运行时,因为我相信 jrt:/ 的文件系统将始终指向当前运行时映像。

我想你想要的是不可能的。即:

  • 最多 JDK8,您可以依赖例如Paths.get(pathToJdk8Home, "jre", "lib", "rt.jar") 存在,然后你可以将其变成 URL(你正在寻找 jar:file:/that/path),然后你可以将 URL 扔到 FileSystems.newFileSystem ),请参阅 this documentation 了解更多信息。
  • 但是从 JDK9 开始,核心 java API 加载到 jmod 个文件中,jmod 文件的格式未指定 - 现在 jmods 只是 zip,但与 jar 不同,您无法明确保证它们将保持 zip 格式,并且没有 jmod URL 方案,没有 JmodFileSystemProvider。实际上,不可能以未来兼容的方式读取 jmod 文件。不幸的是,OpenJDK 项目一直在疯狂地将大量有用的东西(例如 'read a jmod')转化为实现细节。有点用户敌意——只要意识到这一点,我正在尝试做一些期望管理:像这样的事情是方式,方式更难,并且维护负担巨大(因为你被迫陷入变通办法,黑客攻击,和超出规范因此需要检查它是否仍然适用于每个小版本)。另见 .

jrt 方案只能从 jmod 加载实际上是 'loaded' 的数据到 VM 的 mod 基础,我收集的数据显然不是你的想要(事实上,我很确定你不能将 JDK11 核心 jmods 加载到 JDK14 中,因为它已经加载了它的 jmods ,你会得到一个拆分包违规)。根据其规范,jrt:// URL 方案与基本文件系统无关。您指定一个 module 名称(或什么都不指定,并且您将所有加载的 module 作为一个文件系统)。没有地方让你列出 JDK 安装路径或 jmod 文件,所以这也帮不了你。

因此,您只有两个选择:

  • 接受你想要的无法完成的事实。
  • 接受你将不得不编写 hackery(例如,超越规范保证你的东西),并且你接受领土带来的巨大维护负担。

黑客攻击将涉及:

  • 检测目标 JDK 版本或在提供的 JDK 安装目录(使用例如 Files.walk)中进行搜索以查找名为 rt.jar 的文件。如果它在那里,将它作为 ZipFileSystem 加载并继续。模块 'do not exist',只需将任何所需的 class 转换为路径,方法是将点替换为斜杠并附加 .class(请注意,您需要二进制名称;例如 package com.foo; class Outer { class Inner {}} 表示你希望 Inner 的名字是 com.foo.Outer$Inner,这样你就可以把它变成 /com/foo/Outer$Inner.class).
  • 对于 JDK9 及更高版本,在 JDK_HOME/jmods/java.base.jmod 寻找一个文件并将其扔到 ZipFileSystem。给定的 class 在子目录 classes 中。所以,你正在寻找例如zip 中的条目 classes/java/lang/Object.class(jmod 是 zip)。然而,在这段代码上加上注释,指出这是一个完全的 hack,并且零保证这在未来会起作用。不过,我可以告诉你,至少 JDK16 仍然有基于 zip 的 jmod 文件。
  • 或者,如果您有 JDK 安装路径,您可以使用 ProcessBuilder 执行 List.of("JDK_HOME/bin/jmod" /* or jmod.exe, you'll have to check which one to call! */, "extract", "JDK_HOME/jmods/java.base.jmod"),但请注意,这将提取 all 将这些文件放入当前工作目录(您可以将调用进程的 cwd 设置为您刚刚创建的某个目录,以便在其中填充文件)。如果您想要的只是一个文件,那么相当大的火箭筒。 (您也可以改用 --dir 选项)。优点是,即使假设 JDK17 使用某种不同的格式,这仍然有效;大概 JDK17 仍然会有 bin/jmodjmods/java.base.jmod,并且 JDK17 的 bin/jmod 应该能够解压 jmod 文件在您的 JDK17 安装中。即使你是 运行 所有这一切,例如JDK16 无法读取它们。

您仍然可以使用 中描述的 jrt:/ 方案,您只需要在创建 FileSystem 对象时在环境参数中提供替代 java.home 路径:

public static void listModules(String javaHome) throws IOException {
    FileSystem fs = FileSystems.newFileSystem(
            URI.create("jrt:/"),
            Collections.singletonMap("java.home", javaHome));
    try (Stream<Path> stream = Files.list(fs.getPath("/modules"))) {
        stream.forEach(System.out::println);
    }
}

或者,读取单个资源:

public static byte[] readResource(String javaHome, String module, String path) throws IOException {
    FileSystem fs = FileSystems.newFileSystem(
            URI.create("jrt:/"),
            Collections.singletonMap("java.home", javaHome));
    return Files.readAllBytes(fs.getPath("modules", module, path));
}