序
本文主要研究下
multi-release jar (MR JAR)
java9新支持了multi-release jar的功能,包括jar、javac、javap、jdeps等命令都能支持这个特性。所谓multi-release jar可以包含多个jdk版本的实现,在运行时JVM根据当前环境加载符合版本的class,这样可以使得jar包在兼容旧版本的同时尽可能早地尝试新版JDK的特性。
通过--release参数指定编译版本,依赖 来编译成指定JDK版本的class
具体的变化就是META-INF目录下MANIFEST.MF文件新增了一个属性:
Multi-Release: true
然后META-INF目录下还新增了一个versions目录,如果是要支持java9,则在versions目录下有9的目录
实例
java8
- com.example.lang
public class StackHelper { public static String getCurrentStack() { System.out.println("java 8 stack"); return Arrays.stream(Thread.currentThread() .getStackTrace()) .map(element -> element.toString()) .collect(Collectors.joining("\n")); }}
- maven
com.example mr-jar-java UTF-8 1.8 1.8 com.example mr-jar-java9 0.0.1-SNAPSHOT provided org.apache.maven.plugins maven-jar-plugin ${project.artifactId} ${project.artifactId} ${project.version} true org.apache.maven.plugins maven-dependency-plugin add-java9-classes generate-resources unpack-dependencies com.example mr-jar-java9 true ${project.build.directory}/generated-resources/META-INF/versions/9 **/*.class org.codehaus.mojo build-helper-maven-plugin 3.0.0 add-resource generate-resources add-resource ${project.build.directory}/generated-resources/
注意,这里依赖java9的jar包,然后Multi-Release设置为true,通过编译打包把java9的classes放到META-INF/versions/9目录下,原来java8的classes位置不变。
java9
- com.example.lang
public class StackHelper { public static String getCurrentStack() { System.out.println("java 9 stack"); return StackWalker.getInstance() .walk(frames -> frames.map(Object::toString) .collect(joining("\n"))); }}
- module-info.java
module mr.jar.java9 { exports com.example.lang;}
- maven
com.example mr-jar-java9 UTF-8 9 9 org.apache.maven.plugins maven-compiler-plugin 3.6.2 9
打包
mvn clean install
- jar包内容如下
➜ mr-jar-java-0.0.1-SNAPSHOT git:(master) ✗ tree.├── META-INF│ ├── MANIFEST.MF│ ├── maven│ │ └── com.example│ │ └── mr-jar-java│ │ ├── pom.properties│ │ └── pom.xml│ └── versions│ └── 9│ ├── com│ │ └── example│ │ └── lang│ │ ├── StackHelper.class│ │ └── StringHelper.class│ └── module-info.class└── com └── example └── lang ├── StackHelper.class └── StringHelper.class12 directories, 8 files
- 查看MANIFEST.MF
Manifest-Version: 1.0Created-By: Apache Maven 3.3.3Built-By: demoBuild-Jdk: 9Implementation-Title: mr-jar-javaImplementation-Version: 0.0.1-SNAPSHOTMulti-Release: trueSpecification-Title: mr-jar-java
- 确认java8 class版本
➜ mr-jar-java-0.0.1-SNAPSHOT git:(master) ✗ javap -verbose ./com/example/lang/StackHelper.classClassfile /Users/demo/multi-release-jar-demo/mr-jar-java8/target/mr-jar-java-0.0.1-SNAPSHOT/com/example/lang/StackHelper.class Last modified 2018年3月7日; size 1901 bytes MD5 checksum 9fafe51ca3df481e8c2264753c281a9a Compiled from "StackHelper.java"public class com.example.lang.StackHelper minor version: 0 major version: 52 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #15 // com/example/lang/StackHelper super_class: #16 // java/lang/Object interfaces: 0, fields: 0, methods: 3, attributes: 3
可以看到major版本是52
- 确认java9 class版本
➜ mr-jar-java-0.0.1-SNAPSHOT git:(master) ✗ javap -verbose ./META-INF/versions/9/com/example/lang/StackHelper.classClassfile /Users/demo/multi-release-jar-demo/mr-jar-java8/target/mr-jar-java-0.0.1-SNAPSHOT/META-INF/versions/9/com/example/lang/StackHelper.class Last modified 2018年3月7日; size 1921 bytes MD5 checksum da6326681eb1b0584998a92178e22e27 Compiled from "StackHelper.java"public class com.example.lang.StackHelper minor version: 0 major version: 53 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #14 // com/example/lang/StackHelper super_class: #15 // java/lang/Object interfaces: 0, fields: 0, methods: 3, attributes: 3
可以看到major版本是53
运行
- java8
java 8 stackjava.lang.Thread.getStackTrace(Thread.java:1559)com.example.lang.StackHelper.getCurrentStack(StackHelper.java:14)Java8Main.main(Java8Main.java:15)
- java9
java 9 stackmr.jar.java9/com.example.lang.StackHelper.getCurrentStack(StackHelper.java:13)mr.jar.java9.main/mr.jar.java9.main.Java9Main.main(Java9Main.java:17)
注意java9调用multi-release jar的工程,需要requires该module,然后编译运行都需要添加module-path
java --module-path ./target/classes:/Users/demo/.m2/repository/com/example/mr-jar-java/0.0.1-SNAPSHOT/mr-jar-java-0.0.1-SNAPSHOT.jar --module mr.jar.java9.main/mr.jar.java9.main.Java9Main
jar命令
上面的例子是利用maven插件来打包,也可以使用jar来打包multi-release jar,实例如下:
javac --release 8 -d out/8 mr-jar-java8/src/main/java/com/example/lang/StackHelper.javajavac --release 9 -d out/9 mr-jar-java9/src/main/java/com/example/lang/StackHelper.javajar -cfm out/mr-jar-demo.jar ./MANIFEST.MF -C out/8 .jar -uf out/mr-jar-demo.jar --release 9 -C out/9 .
这里的-c表示创建jar包,-C表示转去该目录执行不带-C的jar命令,-f指定jar包的文件名,-m指定manifest.mf文件,-u添加文件到jar包中 其中MANIFEST.MF包含Multi-Release: true
小结
java9提供的multi-release jar的功能,可以在一个jar包打入多个jdk版本,同时在java9及以上的版本支持multi-release。它的好处就是比如从java8到java9的迁移,如果java8依赖的jar本身就是multi-release的,那么升级到java9就比较方便,不用再改maven依赖。不好的地方就是有过度设计的味道,一个jar包含多个版本的class,显得有些冗余。