序
本文主要研究下JEP 238: Multi-Release JAR Files
multi-release jar (MR JAR)
java9新支持了multi-release jar的功能,包括jar、javac、javap、jdeps等命令都能支持这个特性。所谓multi-release jar可以包含多个jdk版本的实现,在运行时JVM根据当前环境加载符合版本的class,这样可以使得jar包在兼容旧版本的同时尽可能早地尝试新版JDK的特性。
通过--release参数指定编译版本,依赖JEP 247: Compile for Older Platform Versions来编译成指定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
<groupId>com.example</groupId>
<artifactId>mr-jar-java</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>mr-jar-java9</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Specification-Title>${project.artifactId}</Specification-Title>
<Implementation-Title>${project.artifactId}</Implementation-Title>
<Implementation-Version>${project.version}</Implementation-Version>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>add-java9-classes</id>
<phase>generate-resources</phase>
<goals>
<goal>unpack-dependencies</goal>
</goals>
<configuration>
<includeGroupIds>com.example</includeGroupIds>
<includeArtifactIds>mr-jar-java9</includeArtifactIds>
<excludeTransitives>true</excludeTransitives>
<outputDirectory>${project.build.directory}/generated-resources/META-INF/versions/9</outputDirectory>
<includes>**/*.class</includes>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>add-resource</id>
<phase>generate-resources</phase>
<goals>
<goal>add-resource</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>${project.build.directory}/generated-resources/</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
注意,这里依赖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
<groupId>com.example</groupId>
<artifactId>mr-jar-java9</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>9</maven.compiler.target>
<maven.compiler.source>9</maven.compiler.source>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.2</version>
<configuration>
<release>9</release>
</configuration>
</plugin>
</plugins>
</build>
打包
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.class
12 directories, 8 files
- 查看MANIFEST.MF
Manifest-Version: 1.0
Created-By: Apache Maven 3.3.3
Built-By: demo
Build-Jdk: 9
Implementation-Title: mr-jar-java
Implementation-Version: 0.0.1-SNAPSHOT
Multi-Release: true
Specification-Title: mr-jar-java
- 确认java8 class版本
➜ mr-jar-java-0.0.1-SNAPSHOT git:(master) ✗ javap -verbose ./com/example/lang/StackHelper.class
Classfile /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.class
Classfile /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 stack
java.lang.Thread.getStackTrace(Thread.java:1559)
com.example.lang.StackHelper.getCurrentStack(StackHelper.java:14)
Java8Main.main(Java8Main.java:15)
- java9
java 9 stack
mr.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.java
javac --release 9 -d out/9 mr-jar-java9/src/main/java/com/example/lang/StackHelper.java
jar -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,显得有些冗余。
doc
- JDK 9 features
- JEP 238: Multi-Release JAR Files
- Building Multi-Release JARs with Maven
- Multi-Release JARs: Good or Bad Idea?
- Creating Multi-Release JAR Files in IntelliJ IDEA
- The (Practical) Truth about Multi-Release JARs
- Understanding Java 9 Multi-Release Jar File with Example
- Multi-Release JARs: Good or Bad Idea?
- Java 9 - Multi-Release Jar Example