尚学堂 老师好!

上海:15201841284

广州:020-2989 6995

深圳:0755-23061965

武汉:027-8798 9193

使用Maven构建多版本JAR

Java 9附带了一个对于库作者非常有用的新功能:多版本JAR (JEP 238)。

多版本JAR(MR JAR)可能包含同一类的多个变体,每个变体都针对特定的Java版本。 在运行时,类的正确变体将被自动加载,这取决于所使用的Java版本。

更多精彩内容以及学习资料,尚学堂论坛bbs.bjsxt.com免费下载。

这允许库作者在早期利用新的Java版本,同时保持与旧版本的兼容性。 假如你的库对变量执行原子比较和设置(compare-and-set)操作,那么你现在可以使用sun.misc.Unsafe类。 由于Unsafe从未用于JDK本身以外的用途,因此Java 9提供了一种支持的替代方法,用于以f var handle形式的CAS逻辑。 通过将库提供为MR JAR,当你在Java 9上运行时,可以受益于var handle,而在旧平台上运行时保持Unsafe

在下面,我们将讨论如何使用Apache Maven创建MR JAR。

多版本JAR的结构

多版本JAR包含几个类文件的树。 主树位于JAR的根,而版本特定的树位于 META-INF/versions下,例如。 像这样:

 JAR root - Foo.class - Bar.class + META-INF    - MANIFEST.MF    + versions       + 9          - Bar.class

这里,来自JAR根的Foo和Bar类将用于不识别MR JAR(即Java 8和更早版本)的Java运行时,而来自JAR根的Foo和来自META-INF / versions / 9的Bar将是 在Java 9和更高版本中使用。 JAR清单(manifest )必须包含一个条目Multi-Release:true,以指示JAR是MR JAR。

示例:获取当前进程的Id

举个例子,假设我们有一个库,它定义了一个类,用于提供它运行的进程(PID)的id。PID应该由一个描述符表示,该描述符包括实际的PID和描述PID提供者的String:

src/main/java/com/example/ProcessIdDescriptor.java

 package com.example;  public class ProcessIdDescriptor {      private final long pid;     private final String providerName;       }

直到Java 8,都没有一种简单的方法来获取正在运行的进程的id。 一个相当简单的方法是解析 RuntimeMXBean#getName() 的返回值,它在OpenJDK / Oracle JDK中实现为“pid @ hostname”。 虽然这种行为不能保证在实现之间可移植,让我们将它作为我们默认 ProcessIdProvider的基础:

src/main/java/com/example/ProcessIdProvider.java

 package com.example;  public class ProcessIdProvider {      public ProcessIdDescriptor getPid() {         String vmName = ManagementFactory.getRuntimeMXBean().getName();         long pid = Long.parseLong( vmName.split( "@" )[0] );         return new ProcessIdDescriptor( pid, "RuntimeMXBean" );     } }

再让我们创建一个简单的主类来显示PID和获取该PID的provider:

src/main/java/com/example/Main.java

 package com.example;  public class Main {      public static void main(String[] args) {         ProcessIdDescriptor pid = new ProcessIdProvider().getPid();          System.out.println( "PID: " + pid.getPid() );         System.out.println( "Provider: " + pid.getProviderName() );     } }

请注意目前为止创建的源文件位于常规src/main/java源目录中。

现在让我们基于Java 9的新的 ProcessHandle API创建另一个 ProcessIdDescriptor  的变体,它最终提供了一种获取当前PID的便携式方法。 此源文件位于另一个源目录src/main/java9中

src/main/java9/com/example/ProcessIdProvider.java

 package com.example;  public class ProcessIdProvider {      public ProcessIdDescriptor getPid() {         long pid = ProcessHandle.current().getPid();         return new ProcessIdDescriptor( pid, "ProcessHandle" );     } }

设置构建

有了所有的源文件,现在是配置Maven的时候了,以便创建一个MR JAR。

需要三个步骤。 第一件事是在  src/main/java9 下编译额外的Java 9源代码。 我希望我可以简单地设置另一个Maven编译器插件的execution,但我找不到一个只编译src/main/java9,而不是再次编译来自 src/main/java的文件的方法。

作为一种变通的办法,Maven Antrun插件可用于为Java 9特定源配置第二个javac运行:

pom.xml文件

 ... <properties>     <java9.sourceDirectory>${project.basedir}/src/main/java9</java9.sourceDirectory>     <java9.build.outputDirectory>${project.build.directory}/classes-java9</java9.build.outputDirectory> </properties> ... <build>     ...     <plugins>         ...         <plugin>             <groupId>org.apache.maven.plugins</groupId>             <artifactId>maven-antrun-plugin</artifactId>             <executions>                 <execution>                     <id>compile-java9</id>                     <phase>compile</phase>                     <configuration>                         <tasks>                             <mkdir dir="${java9.build.outputDirectory}" />                             <javac srcdir="${java9.sourceDirectory}" destdir="${java9.build.outputDirectory}"                                 classpath="${project.build.outputDirectory}" includeantruntime="false" />                         </tasks>                     </configuration>                     <goals>                         <goal>run</goal>                     </goals>                 </execution>             </executions>         </plugin>         ...     </plugins>     ... </build> ...

它使用 target/classes 目录(包含由默认编译产生的类文件)作为类路径,允许引用我们的MR JAR支持的所有Java版本的常见类,例如 ProcessIdDescriptor。 编译的类放入target/classes-java9

下一步是将编译的Java 9类复制到 target/classes 中,以便以后将它们放到生成的JAR中的正确位置。 可使用Maven资源插件这样做:

pom.xml

 ... <plugin>     <groupId>org.apache.maven.plugins</groupId>     <artifactId>maven-resources-plugin</artifactId>     <executions>         <execution>             <id>copy-resources</id>             <phase>prepare-package</phase>             <goals>                 <goal>copy-resources</goal>             </goals>             <configuration>                 <outputDirectory>${project.build.outputDirectory}/META-INF/versions/9</outputDirectory>                 <resources>                     <resource>                         <directory>${java9.build.outputDirectory}</directory>                     </resource>                 </resources>             </configuration>         </execution>     </executions> </plugin> ...

这将把Java 9类文件从target/classes-java9复制到target/classes/META-INF/versions/9

最后,需要配置Maven JAR插件,以便将Multi-Release条目添加到清单文件中:

pom.xml

 ... <plugin>     <groupId>org.apache.maven.plugins</groupId>     <artifactId>maven-jar-plugin</artifactId>     <configuration>         <archive>             <manifestEntries>                 <Multi-Release>true</Multi-Release>                 <Main-Class>com.example.Main</Main-Class>             </manifestEntries>         </archive>         <finalName>mr-jar-demo.jar</finalName>     </configuration> </plugin> ...

就这样,我们为构建一个多版本的JAR将所有东西放在一起。 通过mvn clean package(使用Java 9)触发构建以在目标 目录中创建JAR。

为了看看JAR内容是否正确,通过 jar -tf target/mr-jar-demo.jar列出其内容。 你应该看到以下内容:

 ... com/example/Main.class com/example/ProcessIdDescriptor.class com/example/ProcessIdProvider.class META-INF/versions/9/com/example/ProcessIdProvider.class ...

最后,让我们通过 java -jar target/mr-jar-demo.jar来执行JAR并检查它的输出。 使用Java 8或更早版本时,你将看到以下内容:

 PID: <some pid> Provider: RuntimeMXBean

而在Java 9上,它将是这样:

 PID: <some pid> Provider: ProcessHandle

也就是说来自JAR根的ProcessIdProvider类将在Java 8和更早版本上使用,而在Java 9上将使用META-INF/versions/9中的那一个。

结论

虽然javac,jar,java和其他JDK工具已经支持多版本JAR,但是像Maven这样的构建工具仍然需要迎头赶上。 幸运的是,它暂时可以使用一些插件,但我希望Maven等其他工具在不久的将来可以为创建MR JAR提供适当的开箱即用的支持。

其他人也在考虑创建MR JAR。 例如。 请查看我的同事David M. Lloyd的这篇文章。 David为Java 9特定的类使用单独的Maven项目,然后使用Maven依赖性插件将其复制回主项目。 就个人而言,我更喜欢将所有的来源放在一个项目中,因为我发现这样做更简单,或许是我的一个小怪癖。 具体来说,如果你同时将src/main/java 和src/main/java9,配置为你的IDE中的源目录,你会得到一个关于重复的ProcessIdProvider类的错误。 这可以被忽略(如果你不需要用它,你也可以从IDE中删除src/main/java9作为源目录),但它可能有些烦人。

可以考虑在另一个包中使用Java 9类,例如。 java9.com.example然后在构建项目时使用Maven shade 插件将它们重定位到com.example,虽然这看起来为了一个很小的收益付出了相当多的努力。 最终,如果IDE在单个项目中为不同源和目标目录也添加了MR JAR的支持和多个编译,这也是可取的。

在下面的评论部分中欢迎任何关于这个创建MR JAR的方法或其他方法的反馈。 这个博客的完整源代码可以在GitHub上找到。

更多精彩内容以及学习资料,尚学堂论坛bbs.bjsxt.com免费下载。

  • 北京校区
  • 山西校区
  • 武汉校区
  • 长沙校区
  • 深圳校区
  • 上海校区
  • 广州校区
  • 保定招生办
  • 黑龙江项目办

北京京南校区:北京亦庄经济开发区科创十四街6号院1号楼 赛蒂国际工业园
咨询电话:400-009-1906 / 010-56233821
面授课程: JavaEE+微服务+大数据     大数据+机器学习+平台架构     Python+数据分析+机器学习  人工智能+模式识别+强化学习   WEB前端+移动端+服务端渲染

山西学区地址:山西省晋中市榆次区大学城大学生活广场万科商业A1座702

武汉学区地址:武汉市东湖高新区光谷金融港B22栋11楼
咨询电话:027-87989193

网址:http://www.cssxt.com/
咨询电话:0731-83072091

深圳校区地址:深圳市宝安区航城大道U8智造产业园U6栋3楼
咨询电话:0755-23061965 / 18898413781

上海尚学堂校区地址:上海市浦东新区城丰路650号
咨询电话:021-67690939

广州校区地址:广州市天河区车陂街道大岗路5号中侨广场2栋321室(四号线车陂站D出口,或brt车陂站)
咨询电话:18948349646

保定招生办公室

地址:河北省保定市竞秀区朝阳南大街777号鸿悦国际1101室

电话:15132423123

黑龙江项目办
地点:哈尔滨市松北区博文路青年部落孵化器1层
电话:15321415678
Copyright 2006-2021 北京尚学堂科技有限公司  京ICP备13018289号-19  京公网安备11010802015183  
网站维护:北京尚学堂科技有限公司昌平分公司