上海:15201841284
广州:020-2989 6995
深圳:0755-23061965
武汉:027-8798 9193
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的根,而版本特定的树位于 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。
举个例子,假设我们有一个库,它定义了一个类,用于提供它运行的进程(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