1. 概述
Java 9 引入了一项实用特性 —— Multi-Release JAR(MRJAR),允许我们在同一个 JAR 包中为不同 Java 版本提供特定实现的类文件。这项功能对于库开发者尤其有价值,可以在不破坏旧版本兼容性的前提下,针对新版本 JVM 使用更优的 API。
本文将介绍如何通过 Maven 配置生成 MRJAR,避免“高版本 API 在低版本运行时报错”的经典踩坑问题。
2. Maven 支持 MRJAR 的背景
Maven 是 Java 生态中最主流的构建工具之一,其默认打包能力虽强,但要支持 MRJAR 需要额外配置。核心在于两点:
✅ 正确编译多个 Java 版本的源码
✅ 生成符合规范的 JAR 结构和 MANIFEST 配置
接下来我们通过一个实际例子演示完整流程。
3. 示例项目结构
我们设想一个场景:
希望 DefaultVersion.version()
方法在 Java 8 中使用 System.getProperty("java.version")
,而在 Java 9+ 中改用更现代的 Runtime.version().toString()
。
3.1 Java 8 实现
public class DefaultVersion {
public String version() {
return System.getProperty("java.version");
}
}
3.2 Java 9+ 实现
public class DefaultVersion {
public String version() {
return Runtime.version().toString();
}
}
✅ 注意:两个类同名、同包,但实现不同,分别放在不同的源目录中。
3.3 主程序入口
public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
logger.info(String.format("Running on %s", new DefaultVersion().version()));
}
}
3.4 目录结构设计
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── baeldung
│ │ │ └── multireleaseapp
│ │ │ ├── DefaultVersion.java # Java 8 版本
│ │ │ └── App.java
│ │ └── java9
│ │ └── com
│ │ └── baeldung
│ │ └── multireleaseapp
│ │ └── DefaultVersion.java # Java 9+ 版本
⚠️ 关键点:Java 9+ 的源码放在 src/main/java9
下,这是约定而非强制,但需在 Maven 中显式声明。
4. Maven 配置详解
要让 Maven 正确构建 MRJAR,必须配置两个核心插件:
maven-compiler-plugin
:负责多版本编译maven-jar-plugin
:负责生成正确的 MANIFEST 属性
4.1 Maven Compiler Plugin 配置
我们需要为不同 Java 版本设置独立的编译执行(execution),尤其是 Java 9+ 的部分需要特殊处理。
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<!-- 编译 Java 8 主版本 -->
<execution>
<id>compile-java-8</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</execution>
<!-- 编译 Java 9+ 版本 -->
<execution>
<id>compile-java-9</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>9</release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java9</compileSourceRoot>
</compileSourceRoots>
<multiReleaseOutput>true</multiReleaseOutput>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
关键配置说明:
配置项 | 作用 |
---|---|
<release>9 |
告诉编译器以 Java 9 模式编译,启用新 API |
<compileSourceRoots> |
指定额外的源码路径,避免与主源目录冲突 |
<multiReleaseOutput>true</multiReleaseOutput> |
✅ 自动将编译结果输出到 META-INF/versions/9 |
⚠️ 注意:
<release>
必须设置,否则multiReleaseOutput
不生效,且会导致构建失败。
💡 小贴士:从
maven-compiler-plugin 3.7.1
开始支持multiReleaseOutput
,推荐使用 3.8.0+ 版本。
4.2 Maven JAR Plugin 配置
仅仅编译还不够,JAR 包的 MANIFEST.MF
必须包含 Multi-Release: true
才能被 JVM 识别为 MRJAR。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<archive>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
</plugin>
为什么这一步不可少?
- ❌ 没有该配置 → JVM 只加载根目录下的类,忽略
META-INF/versions/*
- ✅ 有该配置 → JVM 根据当前运行版本自动选择对应实现
5. 测试验证
构建完成后,用不同 JDK 版本运行 JAR,观察输出差异。
使用 Java 8 运行
java -cp target/multirelease-app-1.0.jar com.baeldung.multireleaseapp.App
输出:
[main] INFO com.baeldung.multireleaseapp.App - Running on 1.8.0_252
使用 Java 14 运行
java -cp target/multirelease-app-1.0.jar com.baeldung.multireleaseapp.App
输出:
[main] INFO com.baeldung.multireleaseapp.App - Running on 14.0.1+7
✅ 成功!Java 14 自动选择了 META-INF/versions/9/
下的 DefaultVersion.class
,并使用了 Runtime.version()
新格式。
📌 提示:虽然我们只提供了 Java 9+ 的实现,但 MRJAR 向后兼容所有更高版本(如 Java 17、21),无需重复打包。
6. 总结
通过合理配置 Maven 插件,我们可以轻松实现 一次打包、多版本兼容 的 MRJAR,适用于以下场景:
- 库作者希望在新版本中使用新 API(如 VarHandle、Switch Expressions)
- 维护老系统的同时支持新特性
- 减少维护多套代码的成本
核心要点回顾
- ✅ 使用
maven-compiler-plugin
的multiReleaseOutput=true
自动输出版本化类 - ✅ 设置
<release>
指定目标版本 - ✅ 通过
maven-jar-plugin
添加Multi-Release: true
到 MANIFEST - ✅ 源码按版本分离,避免冲突
所有示例代码已托管至 GitHub:https://github.com/baeldung/core-java-modules/tree/master/core-java-9-new-features