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-pluginmaven-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》

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐