验证使用较新 JDK 构建的库的 API 级别的向后兼容性

Verify backward-compatibility on API level of a library built with a newer JDK

最近,一位队友在我们的Java8代码中使用了以下函数:Matcher.replaceAll​(Function replacer)。 该函数是在Java 9中引入的,但是因为他使用的是较新的编译器,所以API函数只是在JDK的rt.jar中找到并且没有人注意到这一点' 在真实 Java 8 环境下工作。 兼容性设置设置正确,gradle子项目有如下设置:

sourceCompatibility = 1.8
targetCompatibility = 1.8

当我第一次在 Java 5 代码中使用 Java 6 函数 String.isEmpty 时,我遇到了非常相似的问题 - 代码进入发行版并在那里崩溃。

我该怎么做才能强制使用正确的 API。因为它是一个共享库,我是否必须为此 gradle 子项目使用(并安装、维护..)不同的 JDK,或者是否有某种兼容性扫描器通过构建的 jar 运行并检查所有 rt 引用?

正如您所注意到的,这两个兼容性配置不考虑旧版本的 API - 只考虑语法、语义和生成的字节码。

您有两种选择。一种是在您的计算机上安装 JDK 8,并配置 Gradle 以便在编译您的项目时使用它。它看起来像这样:

tasks.withType(JavaCompile) {
    options.fork = true
    options.forkOptions.executable = "$java8Home/bin/javac"
    options.bootstrapClasspath = files("$java8Home/jre/lib/rt.jar")
}

这里的缺点是您首先需要安装 JDK 8,并且由于它可能会安装在不同的位置,您可能需要使用环境变量对其进行配置或 属性(我在这里称它为 java8Home)。

但是,从 Java 9 开始,JDK 现在知道以前版本的文档化 API,您可以 select 使用新的 --release ] 旗帜。如果您使用未记录的 API,这将不起作用,但这意味着您可以使用任何版本的 Java 编译您的项目,并且仍然使生成的 类 与 Java 8 兼容。您可以这样做:

tasks.withType(JavaCompile) {
    if (JavaVersion.current() > JavaVersion.VERSION_1_8) {
        options.compilerArgs.addAll(['--release', '8'])
    }
}

请注意,'if' 语句仅在您仍然需要使用 Java 8 支持 运行 Gradle 时才存在(通过您的 JAVA_HOME 变量).如果你只使用更高版本,它可以被删除,所以你总是设置 'compilerArgs'.

对于某些版本的 Java,可以在较新的 JDK 上构建 Java 代码到较旧的 JDK / JRE 上的 运行。您已经发现 javac--source--target 选项以及相应的 Gradle 设置。您可以做的另一件事是使用 --bootclasspath 告诉 javac 针对旧版本 Java.

的 运行time 库进行编译

由于您使用的是 Gradle,请查看 "gradle-java-cross-compile-plugin" (https://github.com/nebula-plugins/gradle-java-cross-compile-plugin)。我找不到它的任何文档,但它显然涉及 --target--bootclasspath.


话虽如此,我认为交叉编译Java不是一个好的解决方案。

我实际上建议您设置一个持续集成 (CI) 服务器(例如 Jenkins),并为您有兴趣支持的所有 Java 版本安装 JDK .然后设置作业以构建代码 运行 每个 Java 版本的单元测试。

请注意,仅针对较旧的 Java 库编译代码不足以验证向后兼容性。有时库的 行为 会发生变化。您需要 运行 您的测试,并且您的测试需要 覆盖 可能存在兼容性问题的情况。