如何针对Java9之前版本构建一个既模块化又兼容Java版本的库呢

18-12-04 banq
    

如果您是库包或框架的作者,你可能希望看到你的库包在大量应用程序中使用。提升库包使用量的一种方法是使其与旧版Java兼容。同时,你可以考虑对库进行模块化,以使其对充分利用Java平台模块系统(JPMS)的应用程序具有吸引力。

但是,JPMS仅由Java 9和更新版本实现。那么,如何在9之前构建一个既模块化又兼容Java版本的库呢?

模块化jar只是一个普通的jar,它包含一个module-info.class代表模块描述符的文件。通常通过编译相应的module-info.java文件来生成该模块描述符。

在Java 8及更早版本上运行时,将忽略模块描述符,因为module-info不是它的合法的Java标识符。这意味着可以通过为目标库的版本创建普通jar并在其中插入模块描述符来生成与Java 9之前版本兼容的模块化jar。

执行这样的办法的标准方法意味着调用java编译器两次:

  • 编译所有源,除了module-info.java使用适当的javac标记参数才能提供Java 9之前版本的兼容性。
  • 使用Java编译器9+编译module-info.java。

Maven

Maven 描述了一种符合上述策略的构建方法。

另一种解决方案是ModiTect,一个用于Java 9模块系统的Maven插件。ModiTect提供的目标之一是add-module-info,它允许您将模块描述符添加到当前Maven项目生成的JAR中:

<plugin>
    <groupId>org.moditect</groupId>
    <artifactId>moditect-maven-plugin</artifactId>
    <version>1.0.0.Beta1</version>
    <executions>
        <execution>
            <id>add-module-infos</id>
            <phase>package</phase>
            <goals>
                <goal>add-module-info</goal>
            </goals>
            <configuration>
                <jvmVersion>11</jvmVersion>
                <module>
                    <moduleInfoFile>
                        ${basedir}/src/main/java/module-info.java
                    </moduleInfoFile>                
                </module>
            </configuration>
        </execution>
    </executions>
</plugin>

ModiTect使用一种聪明的技术来创建模块描述符,而无需Java编译器。它使用JavaParser分析module-info.java文件,并使用 ASM字节码操作框架生成相应的模块描述符。这样,即使您使用Java 9之前的编译器,它也允许您创建模块化jar。

Gradle

从ModiTect的方法中汲取灵感,我实现了一个Gradle插件, 用于创建面向Java 8或更早版本的模块化jar。

使用这个插件非常简单:build.gradle只需要添加插件并指定库所针对的Java版本:

plugins {
    id 'java'
    id 'org.beryx.jar' version '1.0.0'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8

将module-info.java文件放在src/main/java项目目录中后,您可以使用以下命令构建模块化jar:

./gradlew jar

不需要Java 9+编译器来构建模块化jar,因为该插件使用与ModiTect相同的技术。但是,请记住,此技术无法检查模块描述符的有效性。指向不存在的包,模块,服务或服务实现将不会被发现。

因此,强烈建议通过使用Java 9+编译器和sourceCompatibility与targetCompatibility设置进行额外构建项目来验证模块描述符,好消息是您不需要更改构建脚本来执行此操作。该插件允许您通过设置项目属性来覆盖sourceCompatibility和targetCompatibility值javaCompatibility:

./gradlew -PjavaCompatibility=9 jar

请注意,此项目属性会使用相同的值覆盖两者sourceCompatibility并targetCompatibility

如果插件检测到至少有一个sourceCompatibility并且targetCompatibility有效值> = 9,它会自动应用Chainsaw插件,这会增加对JPMS的支持。这就是为什么在Java 9+兼容模式下运行时,构建脚本不需要进行任何更改。

...