JEP 275:模块化 Java 应用程序打包
概述
将 Project Jigsaw 的功能集成到 Java 打包工具中,包括模块感知和自定义运行时创建。
动机
Java 打包工具(javapackager
)在被要求将运行时环境作为其打包的一部分时,由于 JRE 的体积较大,生成的二进制文件一直非常庞大。Project Jigsaw 将开发一个在 JEP 282 jlink: The Java Linker 中定义的工具,该工具允许创建包含标准模块和 JDK 模块子集的运行时镜像,从而使 Java 打包工具能够减小捆绑运行时镜像的大小。
描述
大部分情况下,Java Packager 的工作流将保持不变。Jigsaw 的新工具将会添加进来,并且在一些情况下会替换掉一些步骤。
仅生成 Java 9 应用程序
Java 打包工具将只创建使用 JDK 9 运行时的应用程序。这将简化许多代码路径和假设,涉及到用于组装应用程序和 Java 运行时的工具。如果用户想要创建一个 Java 8 应用程序,那么随 JDK 8 一起提供的 Java 8 版本的 Java 打包工具将继续工作。我们假设需要同时在 Java 8 和 Java 9 上工作的自包含应用程序的数量基本上为零,因为应用程序自带了其自己的 JVM。
使用 jlink
生成嵌入式的 Java 运行时和应用程序镜像
目前,JRE 被复制,并且从复制的运行时中删除不需要的部分。
Java 链接器工具 jlink
提供了一种生成仅包含所需模块的 JRE 映像的方法。此外,jlink
可能会为其映像生成过程提供一些挂钩(hooks),我们可以利用这些挂钩进一步自定义映像。例如,通过在 jlink
处理过程中添加删除可执行文件或压缩的功能。
Java 打包工具将调用 jlink
来创建一个应用运行时镜像,该镜像将被嵌入到应用镜像中。如果 jlink
失败,Java 打包工具会以相应的错误信息失败退出。预计打包的模块将随 JDK 9 一起发布。
jlink
工具包含一个插件和扩展机制。当使用 jlink
生成应用程序镜像时,我们会与这些机制集成,以便 jlink
过程的输出是符合特定平台布局的应用程序镜像。这将带来一个可喜的副作用,即让应用程序镜像的生成不再依赖于 Java 打包器(Packager)过程。
javapackager
CLI 参数、Ant 任务和 Java Packager API
Java 打包工具新增了与 JEP 261 中指定的 Java 工具链其余部分相匹配的 CLI 参数,用于选项语法和值:
--add-modules <module>(,<module>)*
--limit-modules <module>(,<module>)*
--module-path <path>(:<path>)*
-p <path>(:<path>)*
--module <module>/<classname>
-m <module>/<classname>
要为长选项指定参数,可以使用 --<name>=<value> 或 --<name> <value>。
注意:--module-path
映射到 jlink 的 --module-path
,但具有一个可选的默认值。更多信息见下文。
新的 ANT 任务将来自 <fx:application>、<fx:secondaryLauncher> 和新的 <fx:runtime> 任务。
例如:
<fx:deploy outdir="${bundles.dir}"
outfile="MinesweeperFX"
nativeBundles="all"
verbose="true">
<fx:runtime strip-native-commands="false"> <-- new
<fx:add-modules value="java.base"/>
<fx:add-modules value="jdk.packager.services,javafx.controls"/>
<fx:limit-modules value="java.sql"/>
<fx:limit-modules value="jdk.packager.services,javafx.controls"/>
<fx:module-path value="${java.home}/../images/jmods"/>
<fx:module-path value="${build.dir}/modules"/>
</fx:runtime>
<fx:application id="MinesweeperFX"
name="MinesweeperFX"
module="fx.minesweeper" <-- new
mainClass="minesweeper.Minesweeper"
version="1.0">
</fx:application>
<fx:secondaryLauncher name="Test2"
module="hello.world" <-- new
mainClass="com.greetings.HelloWorld">
</fx:secondaryLauncher>
</fx:deploy>
<fx:runtime>
、<fx:limit-modules>
、<fx:add-modules>
、<fx:modular-path>
是可选参数。如果与模块化应用程序捆绑,则会使用 <fx:application>
上的 module="模块名称"
参数;否则,如果应用程序是非模块化应用程序,则该参数无效。参数 <fx:limit-modules>
、<fx:add-modules>
、<fx:modular-path>
可与此文档中使用的 --add-mods
、--limit-mods
和 --module-path
互换。有关其他模块的参数信息,请参阅 模块配置 部分。
Java 打包器 API 将获得用于模块化选项的新方法。
剥离原生命令
剥离诸如 java.exe
这样的命令一直是 Java 打包工具的默认设置,但一些开发者需要诸如 java.exe
这样的命令行工具。因此,将会有一个选项可以通过关闭命令剥离来包含本地命令:
--strip-native-commands false
添加对模块和模块路径的支持
Jigsaw 引入了“模块路径(module path)”的概念,它是对类路径(classpath)的补充。模块路径由库、JDK 模块和应用程序模块的路径组成。这些模块所在的路径通过命令行参数指定:
--module-path <path>(:<path>)*
它只能被提供一次,并且是一个平台路径。根模块及其传递依赖项被链接在一起,以创建一个模块化的运行时镜像(JEP 220)。
开发者可以提供一条带有打包模块的路径,以使用与默认版本不同的 Java 运行时进行捆绑。如果开发者未提供任何 JDK 打包模块,那么 Java 打包工具将默认使用与 Java 打包工具一起提供的 JDK 版本中的打包模块($JAVA_HOME/jmods)。
Java 打包工具目前未提供将打包的模块复制到应用程序运行时映像,而不是链接到 jimage
的机制。这种场景最有可能的需求是,如果应用程序支持插件,并且这些模块位于捆绑映像之外。如果是这种情况,开发者需要使用用户 JVM 参数覆盖来重写 --module-path 和 --add-modules。
模块配置
使用 Java Packager 打包的 Java 应用程序有两种类型:非模块化 JAR 和模块化应用程序。
非模块化 JAR 由一个不包含 module-info.class
的 JAR 文件组成。对于应用程序,使用 -appClass
和 -BmainJar=
。开发者将使用 Java Packager,并采用与 JDK 9 之前版本相同的参数,例如 -srcfiles
、-Bclasspath=
、-appClass
和 -BmainJar=
。为了向后兼容,不需要新的模块化参数,默认情况下,嵌入的 Java 运行时将包含所有可重新分发的模块,因此捆绑运行时的大小不会减少。开发者可以使用 --module-path
、--add-modules
和 --limit-modules
来包含第三方模块。
例如:
javapackager -deploy -v -outdir output -name HelloWorld -Bclasspath=hello.world.jar -native -BsignBundle=false -BappVersion=1.0 -Bmac.dmg.simple=true -srcfiles hello.world.jar -appClass HelloWorld -BmainJar=hello.world.jar
模块化应用程序由包含 module-info.class
的 JAR 文件、解压的模块或打包的模块组成。要与模块化应用程序捆绑,必须指定 --module
和 --module-path
参数。--module
与 -appClass
和 -BmainJar=
互斥。--module-path
必须提供包含主模块(通过 --module
引用的模块)的路径。其他模块可以使用 --add-modules
和 --limit-modules
添加到 运行时镜像
中。通过核心反射或服务动态加载的模块必须使用 --add-modules
手动指定。主模块和通过 --add-modules
提供的模块将定义根模块。jlink
将创建一个包含指定根模块及其传递依赖项的运行时镜像。
例如:
javapackager -deploy -v -outdir output -name Test -native -BsignBundle=false -BappVersion=1.0 -Bmac.dmg.simple=true --module-path /path/to/jmod --module hello.world/com.greetings.HelloWorld
此命令将生成一个运行时镜像,其中包含主模块及其所有传递依赖项。可以通过 --add-modules
选项添加其他模块。
模块
打包器将被拆分为两个模块:
jdk.packager
jdk.packager.services
jdk.packager
包含用于构建应用程序包和安装程序的 Java Packager。
jdk.packager.services
是一个与应用程序包捆绑在一起的模块,它在运行时提供对打包服务的访问,例如 JVM 用户参数。
JNLP
生成的包将取决于所提供的输入和选项。历史上,-deploy
会生成所有的本地包和 .jnlp
文件。现在,-deploy
与 -module
结合使用时将不再生成 .jnlp
文件,因为 JNLP 不支持新的模块化选项。不带任何选项的 -native
将生成所有可用的本地包。
测试
首先,JDK 8 中使用的现有 API、命令行和 Ant 调用的 Java Packager 在 JDK 9 中应该同样可以运行,因此应该运行 JDK 8 packager 的现有测试。
需要编写新的测试来运行新暴露的标志,以支持 run-time image
生成、模块路径规范和 jeeps
进程交互。
风险与假设
我们假设该项目将基本按照描述的方式交付。如果较大的功能部件被移到后续版本中,例如模块路径和模块系统,那么本 JEP 的相应部分也会顺延。