用自定义版本替换 Java class 库中的 class

Replace a class within the Java class library with a custom version

javax/swing/plaf/basic 中的 class BasicLabelUIconfirmed bug 影响。 在我的应用程序中,我需要 fixed version (filed for v9) 提供的功能。 由于法律和技术原因,我仍然受限于受影响的 JDK 版本。

我的方法是在我的项目中创建一个包 javax/swing/plaf/basic,包含固定版本。

如何强制我的项目优先使用我包含的 class 版本而不是安装的 JDK 中有缺陷的 class?

这必须是可移植的,因为固定的 class 也必须在客户端工作,并且必须忽略 JDK 安装中有缺陷的 class。因此,我不想修改 JDK,而是绕过这个特定的 class。

How can I force my project to favor my included version of the class over the defective class in the installed JDK?

简单的答案 - 你不能。至少,在严格遵守应该使用受影响的 Java 版本的约束的同时。

假设您可以在 OpenJDK 源代码库中找到合适的版本,就可以构建您自己的 Java 库,并修复了错误。然而,那不会是真的 Java。当然,它不符合您被限制使用的“受影响的 Java 版本”。 (此外,您正致力于将您的补丁重新应用到 Java 当前版本的每个新补丁版本的无休止循环......)

理论上也可以将某些 Java 标准库 class 的修改版本放入 JAR 并将其添加到 JVM 的 bootstrap class 路径中使用 -Xbootclasspath 命令行选项。但这也等于改变“受影响的 Java 版本”。

通过使用 Java 代理来使用 class 的补丁版本也违反了规则。而且它更复杂。 (如果你要打破你的规则,就用简单的方法...)


如果您和您的客户确实认为调整 JVM 是一个可以接受的解决方案,那么通过 bootstrap class 路径进行调整可能是最简单和最干净的方法。而且绝对合法1.

但是,我建议您在包含您的修复程序的 Java 9 版本发布之前找到该错误的解决方法。


1 - 实际上,即使是从修改源构建的方法也是合法的,因为 Oracle 二进制许可证不适用于此。二进制许可证是关于分发 Oracle 二进制文件的修改版本。另一个可能的问题是,如果您分发与“真实”Java 不兼容的版本并调用您的发行版“[=36],则您可能违反了使用 Java 商标的条款=]”。解决方案是......不要称它为“Java”!

但是,不要只听从我的建议。问律师。更好的是,根本不要这样做。它不必要地复杂。

如其他答案所述,理论上您 可以 解压缩 JVM 的 rt.jar 文件并替换具有兼容错误修复版本的文件。

Java Class 库的任何 class 类,例如 Swing 的库,都由 bootstrap class 加载器加载 从这个 rt.jar 中查找它的 classes。您通常不能 prepend classes 到此 classpath 而不将它们添加到此文件中。有一个(非标准)VM 选项

-Xbootclasspath/jarWithPatchedClass.jar:path

您可以在其中添加包含修补版本的 jar 文件,但这不一定适用于任何 Java 虚拟机。此外,部署改变此行为的应用程序是非法的!正如 official documentation:

中所述

Do not deploy applications that use this option to override a class in rt.jar because this violates the Java Runtime Environment binary code license.

但是,如果您将 class 附加到 bootstrap class 加载器(通过使用仪器 API),运行时仍会加载原始的 class,因为 bootstrap class 加载程序在这种情况下首先搜索 rt.jar .因此,如果不修改此文件,就不可能 "shadow" 损坏 class。

最后总是illegal to distribute a VM with a patched file,即为客户将其放入生产系统。许可协议明确规定您需要

[...] distribute the [Java runtime] complete and unmodified and only bundled as part of your applets and applications

因此,不建议更改您分发的 VM,因为一旦被发现,您可能会面临法律后果。

当然,理论上您可以构建您自己的 OpenJDK 版本,但是当您分发它时您不能再调用二进制文件 Java 我假设根据您在回答中的建议,您的客户不会允许这样做。根据经验,许多安全环境会在执行前计算二进制文件的哈希值,这会禁止这两种调整正在执行的 VM 的方法。

最简单的解决方案可能是创建一个 Java agent 并在启动时将其添加到 VM 进程中。最后,这与将库添加为 class 路径依赖项非常相似:

java -javaagent:bugFixAgent.jar -jar myApp.jar

Java 代理能够在应用程序启动时替换 class 的二进制表示,因此可以更改错误方法的实现。

在您的情况下,代理将类似于以下内容,您需要将修补的 class 文件作为资源包括在内:

public static class BugFixAgent {
  public static void premain(String args, Instrumentation inst) {
    inst.addClassFileTransformer(new ClassFileTransformer() {
      @Override
      public byte[] transform(ClassLoader loader, 
                              String className, 
                              Class<?> classBeingRedefined, 
                              ProtectionDomain protectionDomain, 
                              byte[] classfileBuffer) {
        if (className.equals("javax/swing/plaf/basic/BasicLabelUI")) {
          return patchedClassFile; // as found in the repository
          // Consider removing the transformer for future class loading
        } else {
          return null; // skips instrumentation for other classes
        }
      }
    });
  }
}

javadoc java.lang.instrumentation 包详细描述了如何构建和实施 Java 代理。使用这种方法,您可以在不违反许可协议.

的情况下使用有问题的class的固定版本

根据经验,Java 代理是修复第三方库和 Java Class 库中临时错误的好方法,无需在您的代码中部署更改,甚至需要为客户部署新版本。事实上,这是使用 Java 代理的典型用例。