通过提供Java 9 module-info.class
来了解如何使用Gradle构建支持JPMS( Java平台模块系统 )的Java 6-8库。
介绍
如果您需要JPMS本身的介绍,请查看此概述 。
这篇文章主要针对Java库维护者。
任何此类维护者都必须选择要针对的JDK:
- 针对最新的JDK( JDK 11或刚刚发布的JDK 12 ),开发人员和用户可以使用新的API和更多功能。
- 但是,这会阻止所有使用较旧JDK的用户使用该库。
- 那些较老的JDK仍然很受欢迎, 在2018年约占95%的份额 ,并预计在2019年将约90%的 份额 。
因此,对于许多图书馆维护者而言,后者无疑是一个决定性因素 。 例如, vavr 1.0 旨在以JDK 11为目标 ,但最终将以JDK 8为目标 。
尽管如此,还是建议为JPMS添加一些支持,以期将来会被广泛采用(我想从现在起5年以上)。 Stephen Colebourne在这里描述了三个选项:
- 不执行任何操作(不建议)。
- 最低要求:在
MANIFEST.MF
文件中添加一个“Automatic-Module-Name
条目。 - 最佳:添加一个针对JDK 9+的
module-info.class
同时提供所有其余针对JDK 6-8 *的类。
在这里,我们将深入研究如何实现选项3(最佳选择)。
*我写的是JDK 6-8(而不是JDK 5-8),因为在JDK 11中, javac
的--release
选项限制为6-11。
理由
不过,在深入研究“如何”之前,让我们先略过“为什么”。
为什么JPMS完全值得打扰? 主要是因为JPMS:
- 提供强大的封装 ,
- 防止引入拆分包 ,
- 确保更快的类加载 。
综上所述,JPMS 非常酷 (更多信息请参见此处 ),鼓励我们采用JPMS是我们的最大利益!
因此,我鼓励Java 6-8库的维护者充分利用JPMS:
- 自己针对模块的JDK 6-8类和其他模块编译
module-info.java
, - 对于他们的用户,通过提供
module-info.class
来使库在module-path上正常工作。
可能的行为
可以在两个位置找到module-info.java
:
- 与其他所有类,在
src/main/java
, - 在单独的“源集”中,例如
src/main/java9
。
我喜欢选项1,因为它看起来更自然。
module-info.class
可以在两个地方结束:
- 在根输出目录以及所有其他类中,
- 在
META-INF/versions/9
(多发行版JAR,又名MRJAR )
阅读了塞德里克·尚波( CédricChampeau)关于MRJAR的帖子后 ,我对MRJAR颇为怀疑,因此我更喜欢选项1。
但是请注意, Gunnar Morling报告说选项1存在一些问题。另一方面,我希望距JDK 9发行1.5年后,所有主要库都已打补丁以正确处理module-info.class
。
每个构建工具的示例库
本节包含一些在针对JDK 6-8时提供module-info.class
的库示例。
蚂蚁
- Lombok (JDK 6 main + JDK 9
module-info.class
)
马文
- ThreeTen-extra (JDK 8 main + JDK 9
module-info.class
) - Google Gson –尚未发布(JDK 6 main + JDK 9
module-info.class
) - SLF4J –尚未发布(
META-INF/versions/9
JDK 6 main + JDK 9module-info.class
)
请注意, Maven编译器插件提供了如何提供这种支持的示例 。
摇篮
我还没有找到任何流行的库使用Gradle提供这种支持(请注释,如果您知道的话)。 我只知道vavr试图这样做( #2230 )。
Gradle中的现有方法
修改
ModiTect (由Gunnar Morling编写 )及其Gradle插件 (由Serban Iordache编写 )具有一些非常酷的功能 。 本质上,ModiTect基于特殊符号或直接从module-info.java
生成 module-info.class
-info.class, 而无需使用javac
。
但是,在从module-info.java
直接生成的情况下,ModiTect有效地复制了javac
所做的操作,同时引入了自己的问题(例如#90 )。 这就是为什么我觉得它不是最好的工具。
Badass Jar插件
Serban Iordache还创建了一个Gradle插件 ,该插件可以“无缝创建针对9之前的Java版本的模块化jar”。
看起来不错,但是:
- 为了构建适当的JAR并验证
module-info.java
,Gradle构建必须运行两次, - 它不使用
javac
的--release
选项,该选项保证仅引用正确的API, - 它不使用
javac
来编译module-info.java
。
同样,我觉得这里不是正确的工具。
JpmsGradlePlugin
这是我最近的发现: JpmsGradlePlugin由阿克塞尔Howind 。
该插件可以做一些不错的事情(例如,从javadoc
任务中排除module-info.java
),但是:
- 它也不使用
javac
的--release
选项, - 它不完全支持Java模块化(例如,模块修补),
- 感觉还不够成熟( 难以遵循的代码,非标准行为,例如直接调用
javac
)。
Gradle中的拟议方法
摇篮脚本
最初,我想通过添加自定义源集来做到这一点 。 但是,事实证明,这种方法会引入不必要的配置和任务 ,而我们真正需要的只是一个额外的任务,它正确地“钩住”了构建生命周期 。
结果,我想到了以下几点:
- 将
compileJava
配置为:- 排除
module-info.java
, - 使用
--release 6/7/8
选项。
- 排除
- 添加一个名为
compileModuleInfoJava
的新JavaCompile
任务,并将compileModuleInfoJava
配置为:- 仅包含
module-info.java
, - 使用
--release 9
选项, - 使用
compileJava
的类路径作为--module-path
* , - 使用
compileJava
*的目标目录 , - 取决于
compileJava
* 。
- 仅包含
- 配置
classes
任务以依赖于compileModuleInfoJava
。
以上内容在Groovy DSL中表示为Gradle脚本,可以在我的Stack Overflow答案中找到。
*这三个步骤对于compileModuleInfoJava
查看由compileJava
编译的类是compileJava
。 否则,由于未解析的引用, javac
将无法编译module-info.java
。 请注意,在这种配置中,每个类仅被编译一次 (与推荐的Maven Compiler Plugin配置不同 )。
不幸的是,这样的配置:
- 在存储库之间不容易重用,
- 不完全支持Java模块化。
Gradle模块插件
最后,有一个插件( Gradle Modules Plugin ),它为Gradle添加了对JPMS的完全支持(由Java 9 Modularity的作者Sander Mak和Paul Bekker创建 )。
该插件仅不支持本文所述的方案。 因此,我决定:
- 通过此插件提交功能请求: #72
- 提供具有#72的完整实现的“拉取请求”(作为“概念证明”): #73
我尽力做出这些高质量的贡献。 最初的反馈意见非常受欢迎 (甚至Mark Reinhold都喜欢!)。 谢谢!
现在,我正在耐心地等待进一步的反馈 (以及潜在的改进要求),然后才能(希望)合并PR。
摘要
在本文中,我展示了如何使用Gradle构建Java 6-8库,以便将module-info.java
编译为JDK 9格式(JPMS支持),而将所有其他类编译为JDK 6-8格式。
我还建议对这种配置使用Gradle Modules插件 (一旦我的PR合并并且发布了新的插件版本)。
翻译自: https://www.javacodegeeks.com/2019/03/building-java-6-8-libraries-jpms-gradle.html