common-java是一个我维护了好多年的一个基础项目,编译目标为Java 1.7
现在整个团队的项目要做Java 9以上的技术迁移准备,就需要对这个在内部各项目中被广泛引用的基础项目进行改造,以适合Java 9的模块化规范。
Automatic-Module-Name
Java 9的模块化规范(即Java Platform Module System [JPMS])要求在项目中有module-info.java,
会在module-info.java中定义模块名
module-info.java定义模块名示例如下
module your.module.name{
//
}
JPMS
可以兼容Java 1.7或1.8的依赖库.
在Java 9平台如果一个库没有module-info.class,那么会将它识别为自动模块(automatic module).会根据jar包的名字自动生成模块名(Module Name).
因为这个模块名是根据Jar包名字计算出来的,是不稳定的(比如手工改了Jar包文件名,模块名也会自动改为).
所以这不是JPMS
建议的方式,所以在Java 9环境中引用自动模块时,编译过程会输出警告.
[WARNING] ******************************************************************************************************************************************************************************************************************************************************************************************************
[WARNING] * Required filename-based automodules detected: [guava-20.0.jar, jsr305-1.3.9.jar, fastjson-1.2.83.jar, jackson-databind-2.8.10.jar, jackson-core-2.8.10.jar, sql2java-base-3.29.3.jar, openbeans-1.0.2.jar, jcifs-ng-2.1.2.jar]. Please don’t publish this project to a public artifact repository! *
[WARNING] ******************************************************************************************************************************************************************************************************************************************************************************************************
对于一个编译目标为Java 1.7或1.8的项目,如果项目结构与Java 9的模块化要求不存在冲突.升级到Java 9并不复杂.
只要如下加一个maven-jar-plugin
插件的配置,指定在生成Jar包中META-INF/MANIFEST.MF
中增加Automatic-Module-Name
定义,显式指定自动模块的名字.就可以让一个项目基本适合JPMS
.可以正常被其他Java 9项目在module-info.java中引用。
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifestEntries>
<Automatic-Module-Name>your.module.name</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
Multi-Release Jar
采用上一节的方案生成的jar,在被Java 9项目引用时还会输出警告,示例如下:
[WARNING] /J:/javadocreader9/src/main/java/module-info.java:[7,34] 需要自动模块的过渡指令
引用自动模块报警的项目的module-info.java定义:
module com.gitee.l0km.javadocreader{
exports com.gitee.l0km.javadocreader;
requires java.desktop;
requires transitive jdk.javadoc;
requires static com.google.common;
requires transitive com4j.base2;
requires transitive com4j.base;// [WARNING]:需要自动模块的过渡指令
requires aocache;
requires org.slf4j;
}
如果只是想消除这行警告,只需要将
requires transitive com4j.base;
改为requires static com4j.base;
也就是说对于一个Java 9项目如果module-info.java中引用了有Automatic-Module-Name
定义了模块名的项目jar包,也仍然会有编译警告,它仍然不是JPMS满意的jar包.
要想让一个项目完全符合JPMS规范,就需要为它定义module-info.java,在module-info.java中显式定义项目的模块名,导出的包名等等.
参见 《Java 模块化指南》
定义module-info.java这本身不是问题.
问题在于我们的系统中还有一些android设备仍然在使用Java 1.7。
common-java这个项目也仍然被运行在这些Java 1.7的android设备上的APP引用.
如果增加module-info.java定义,项目的编译目标就要升级到Java 9,就不能用于Java 1.7的平台了.这肯定是不能接受的.
有没有一个两全其美的解决方案呢?
Multi-Release Jar (MRJAR)
(多版本兼容Jar)是Java 9的一个新特性,就是为了解决这个麻烦而诞生的。
它允许将支持多个Java版本不特性的版本打包在同一个Jar包中,系统在运行时自动根据当前的Java版本,从Jar包选择对应Java版本的class加载.
即扩展 JAR 文件格式以允许多个特定于 Java 版本的 类文件的版本共存于单个存档(Jar)中。
详细说明参见:《JEP 238: Multi-Release JAR Files》
有Multi-Release Jar (MRJAR)
这个特性,事儿就好办了,还以common-java这个项目为例,分两个步骤:
(一) java.9
首先定义module-info.java
创建一个一个源文件夹${project.basedir}/src/main/java.9
,在该文件夹下定义module-info.java
比如:
module com4j.base{
exports com.gitee.l0km.com4j.base;
exports com.gitee.l0km.com4j.base.encrypt;
exports com.gitee.l0km.com4j.base.exception;
exports com.gitee.l0km.com4j.base.web;
requires static jackson.annotations;
}
(二)Multi-Release
更新pom.xml,如下增加maven-jar-plugin
和maven-compiler-plugin
插件定义
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<executions>
<execution>
<id>default-compile-java-7</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>7</release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java</compileSourceRoot>
</compileSourceRoots>
</configuration>
</execution>
<execution>
<id>compile-java-9</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>9</release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java.9</compileSourceRoot>
</compileSourceRoots>
<multiReleaseOutput>true</multiReleaseOutput>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
在maven-jar-plugin
插件中定义Multi-Release
,即在生成的Jar中META-INF/MANIFEST.MF
中定义Multi-Release
为true,将Jar标志为支持Multi-Release
.
在maven-compiler-plugin
插件中定义两个执行过程(<execution></execution>
),
一个execution (default-compile-java-7)用于Java 1.7版本的编译,编译${project.basedir}/src/main/java
源码文件夹下的所有主要代码.
另一个execution(compile-java-9
)用于编译${project.basedir}/src/main/java.9
,只有一个文件module-info.java,编译生成人代码保存到META-INF/versions/9
,即Java 9对应的版本.
重新执行maven install
生成的Jar包如下,Jar中除了:META-INF/versions/9/module-info.class外,主要代码代码的编译目标仍然为Java 1.7。因为有module-info.class
提供模块定义,该Jar包在Java 9以上的平台上运行时,就是个符合JPMS要求的Module.
META-INF/MANIFEST.MF
中定义如下:
Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.4.2
Build-Jdk-Spec: 19
Multi-Release: true
注意
如上改造了pom.xml后,不能再使用Java 1.7或1.8编译器构建项目,要使用Java 9以上编译器(我用的是Java 19)
common-java码云仓库位置:https://gitee.com/l0km/common-java
参考资料
《JDK 9 模块化系统 (Java Platform Module System) 和 多版本兼容 Jar (Multi-Release Jar)》
《JEP 238: Multi-Release JAR Files》
《Java 模块化指南》
《compiler:compile》
《Apache Maven JAR Plugin》