JEP-238引入了对多版本 jar 的支持。这意味着您可以在一个 jar 中包含 Java 版本相关的类。根据运行时,它将选择一个类的最佳匹配版本。
这个 JEP 试图解决的问题是即使代码库必须与早期版本保持兼容,也可以使用新的 JDK 功能。
让我们试着用一个真实的例子来具体化:在 Java 7中添加了java.nio,它在文件处理方面要好得多。在那些日子里,Maven 仍然需要 Java 6 才能运行,但是当 Maven 在 Java 7 或更高版本上运行时,他们想利用这些新特性。
在 Java 8 之前,有两种解决方案:
if ( isAtLeastJava7() ) { Method toPathMethod = f.getClass().getMethod( "toPath" ); Object toPathInstance = toPathMethod.invoke( f ); Method isSymbolicLink = Class.forName( "java.nio.file.Files" ).getMethod( "isSymbolicLink" ); ... } else { // compare absoluteFile with canonicalFile ... }
if ( isAtLeastJava7() ) { return Files.isSymbolicLink( f.toPath() ); } else { // compare absoluteFile with canonicalFile ... }
多版本 jar 背后的理论非常简单,但在实践中它可能变得非常复杂。您必须确保所有课程保持同步;如果您将一个方法添加到一个类,不要忘记将它添加到其他类。有一些选项可以减少这个级别的问题:让所有多版本类实现一个接口,并在基码中实例化该类,但只调用接口方法。最好的方法是使用所有目标 Java 版本测试 *jar*。在将 jar 变成多版本 jar 之前,您应该三思而后行,因为这样的 jar 可能难以阅读、维护和测试。通常应用程序不需要这个,除非它是一个广泛分布的应用程序并且您不控制目标 Java 运行时。图书馆应根据以下因素做出决定:我需要这个新的 Java 功能吗?我可以将此 Java 版本作为新要求吗?使用第一段中提到的 else/if 语句来解决这个问题是否可以接受?
在创建 Multi Release jar 时,应该知道几个重要的事实。
这是 Maven 团队自己提供的第一个模式。他们有以下要求:
涵盖前两个项目符号的唯一解决方案是将代码拆分为 Maven 多模块项目。现在每个 Maven 模块只是一个标准的 Maven 项目,在 pom.xml 中几乎没有特定的调整。您可以通过多种方式运行此项目:
即使结果只是 1 个工件,它也需要分层结构的缺点。
此解决方案是对先前 Maven 多模块设置的响应。要求几乎一样
第一个要求意味着这些项目现在是单独的 Maven 项目。一个 Maven 项目包含基本代码,最终这也将是多版本 jar。第一次构建此类项目时,您需要调用 Maven 至少 3 次:首先需要编译基础,接下来必须构建所有版本特定的项目,最后需要再次构建主项目,现在包括从 jar 中提取的特定于版本的类。此设置很紧凑,但具有循环依赖性。这需要一些技巧,并使发布变得更加复杂。另一个缺点是您必须将 SNAPSHOT 安装到本地存储库,并且在发布时它需要基础项目的 2 个版本,一个为 multirelease-nine 做准备,一个为已发布的 multirelease-nine 做准备。
到目前为止,有 3 个解决方案,每个解决方案都受到其先前版本的启发。
主要目标:
在这种情况下,一切都保留在一个 Maven 项目中。每个特定的 Java 版本都有自己的源文件夹和输出文件夹,并且在打包之前将它们组合在一起。没有涵盖的是如何测试每个类。
这种方法用 maven-compiler-plugin 中的额外执行块替换了 maven-ant-plugin。它已设置为父级,因此其他项目可以使用它。它使用工具链来构建具有匹配 Java 版本的所有类,因此您始终可以获得多版本 jar。由于巨大的配置,而且由于 Maven 还不支持 mixin,所以将它全部放在父级中是有意义的。然而,同时surefire只被调用一次。
这种方法通过只为特定 Java 版本的源指定执行块来减少以前的解决方案。它不使用工具链,而是使用 JDK 来运行 Maven。这意味着只有特定 Java 版本的源代码才会被编译和测试。该解决方案严重依赖于每个目标 Java 版本都可用的 CI 服务器。如果 CI 服务器成功,则所有类都使用其匹配的 Java 版本进行测试。
这种方法引入了一种新的打包类型,并且一个额外的插件负责 maven-compiler-plugin 的多次执行,但这些现在由multi -release-jar-maven-plugin的perReleaseConfiguration处理。没有涵盖的是如何测试每个类。
对于每个模式,都会基于相同的源文件集创建集成测试。见https://github.com/apache/maven-compiler-plugin/tree/master/src/it/multirelease-patterns
Maven 多模块 | 多项目 | 单个项目(运行时) | 单个项目(工具链) | Maven扩展+插件 | |
---|---|---|---|---|---|
# 项目 | 1 | 1 + #java版本 | 1 | 1 | 1 |
# 构建打包 | 1 | 2 + #java版本 | 1 | 1 | 1 |
# 构建/项目来测试 | 1 | 1 | #java版本 | 1 | 不适用(一) |
简单的 Maven 项目布局 | 不 | 是的 | 是的 | 是的 | 是的 |
额外的 POM 调整(b) | 1(c) | #java版本(d) | #javaVersion(e) | ??(F) | #java版本(g) |
包含模块描述符 | 否 (h) | 否 (h) | 是的 | 是的 | 是的 |
IDE 支持 (i) | 是的 | 是的 | 不 | 不 | 不 |
(a) 项目只能使用最高要求的 JDK 执行,因此您不能测试所有 JDK 的代码
(b) 额外的 POM 调整:添加到默认生命周期的执行次数。这反映了 POM 的复杂性。
(c) maven multimodule 使用maven-assembly-plugin 组装成multirelease jar
(d) 多项目使用 maven-dependency-plugin 将 java 特定依赖解包到其匹配的 outputDirectory
(e) 每个所需的 Java 版本都有一个配置文件,其中包含该 Java 版本的额外执行块。
(F)
(g) Maven扩展+插件隐藏perReleaseConfiguration配置中的多次执行
(h) 在依赖项上需要一个 --patch-module
(i) IDE 支持:所有类都被识别并且可以在 IDE 中进行测试。