bigywater.tech
Java · 227

多版本 JAR:同一坐标适配不同 Java 版本

一个 JAR 里可以放多套针对不同 Java 版本的实现。这篇真实打包、在三个 JDK 上运行,验证 Multi-Release 属性、系统属性开关、jar 工具的检查,以及 Maven 的构建配置。

17 / 21 / 21
同一个 mr.jar 在 JDK 17 / 21 / 27 上加载的版本
base
去掉 Multi-Release: true 之后加载到的实现
rc = 1
jar 工具对公共 API 不一致的版本化类的返回码

jar 工具打包并在 3 个 JDK 上运行

实测 · 本机执行输出
  jar tf mr.jar:
    META-INF/
    META-INF/MANIFEST.MF
    lib/
    lib/Impl.class
    META-INF/versions/17/
    META-INF/versions/17/lib/
    META-INF/versions/17/lib/Impl.class
    META-INF/versions/21/
    META-INF/versions/21/lib/
    META-INF/versions/21/lib/Impl.class
  META-INF/MANIFEST.MF 内容: 'Manifest-Version: 1.0\r\nCreated-By: 21.0.12.1 (Homebrew)\r\nMulti-Release: true'
  JDK 17 加载:versions/17 的实现(用了 record 之前的 Java 17 API:xx);id=17;Impl 来自 mr.jar
  JDK 21 加载:versions/21 的实现(1);id=21;Impl 来自 mr.jar
  JDK 27 加载:versions/21 的实现(1);id=21;Impl 来自 mr.jar
  应用 Main 是用 --release 8 编译的,与 JDK 无关;同一个 mr.jar,JDK 17 取 versions/17,JDK 21 与 27 取 versions/21(没有 versions/27 时取最高的 ≤ 当前版本)

Multi-Release 属性与系统属性

实测 · 本机执行输出
  去掉 Multi-Release 之后的 MANIFEST.MF: 'Manifest-Version: 1.0\r\nCreated-By: 21.0.12.1 (Homebrew)' ;jar 里仍有 META-INF/versions/21/lib/Impl.class: True
    JDK 21 加载:base(Java 8 版本);id=8;Impl 来自 mr_nomanifest.jar
  (JDK 21)去掉属性后,jar 里的 versions 目录被当作普通目录,只用 base 类
  JDK 21 加 -Djdk.util.jar.enableMultiRelease=false(jar 本身是正确的多版本包):
  JDK 21 加载:base(Java 8 版本);id=8;Impl 来自 mr.jar
  JDK 21 加 -Djdk.util.jar.version=17:
  JDK 21 加载:versions/17 的实现(用了 record 之前的 Java 17 API:xx);id=17;Impl 来自 mr.jar
  JDK 21 加 -Djdk.util.jar.version=9(没有 versions/9,取 base):
  JDK 21 加载:base(Java 8 版本);id=8;Impl 来自 mr.jar

jar 工具的检查与其它加载方式

实测 · 本机执行输出
  版本化的 Impl 比 base 多一个 public 方法 extra() → jar rc = 1 ; ['entry: META-INF/versions/17/lib/Impl.class, contains a class with different api from earlier version', 'invalid multi-release jar file bad1.jar deleted']
  版本化目录里有 base 里不存在的 public 类 Newcls → jar rc = 1 ; ['entry: META-INF/versions/17/lib/Newcls.class, contains a new public class not found in base entries', 'invalid multi-release jar file bad2.jar deleted']
  --release 8(版本化目录要求 ≥ 9)→ jar rc = 1 ; ['release 8 not valid, must be >= 9', "Try `jar --help' for more information."]
  JarFile(runtimeVersion = 8).getEntry("lib/Impl.class").getRealName() = lib/Impl.class,isMultiRelease = true
  JarFile(runtimeVersion = 17).getEntry("lib/Impl.class").getRealName() = META-INF/versions/17/lib/Impl.class,isMultiRelease = true
  JarFile(runtimeVersion = 21).getEntry("lib/Impl.class").getRealName() = META-INF/versions/21/lib/Impl.class,isMultiRelease = true
  URLClassLoader(jar) 在 JDK 17 上加载 lib.Impl → id = 17
  同样用 jar: URL 加载 → id = 17
  JarFile(runtimeVersion = 8).getEntry("lib/Impl.class").getRealName() = lib/Impl.class,isMultiRelease = true
  JarFile(runtimeVersion = 17).getEntry("lib/Impl.class").getRealName() = META-INF/versions/17/lib/Impl.class,isMultiRelease = true
  JarFile(runtimeVersion = 21).getEntry("lib/Impl.class").getRealName() = META-INF/versions/21/lib/Impl.class,isMultiRelease = true
  URLClassLoader(jar) 在 JDK 21 上加载 lib.Impl → id = 21
  同样用 jar: URL 加载 → id = 21

Maven 构建

实测 · 本机执行输出
  mvn package rc = 0 
  Maven 产出的 jar 里的条目: ['META-INF/versions/11/lib/Impl.class', 'lib/Impl.class'] ;Multi-Release 行: ['Multi-Release: true']
  JDK 17 加载:versions/11 的实现(xx);id=11;Impl 来自 mr-1.jar
  JDK 21 加载:versions/11 的实现(xx);id=11;Impl 来自 mr-1.jar
  jar --describe-module: No module descriptor found. Derived automatic module.

mr@1 automatic
requires java.base mandated
contains lib

分步演示

多版本 JAR
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 17.0.20、21.0.12、27;Maven maven-compiler-plugin 3.13.0、maven-jar-plugin 3.4.2
    • 没有验证:模块化的多版本 JAR(版本目录里放 module-info.class)、模块路径下的加载、与 shade/assembly 插件合并后多版本条目的保留、Gradle 的 multi-release 配置、JDK 8 运行时(没有安装)的实际行为
    京ICP备2026067502号-1京公网安备11010502063487号