概述

在部署或交付以模块化体系结构编写的程序模块之前,必须将其打包为工件,以便于将其用作 Java 程序或其他模块的组件。通常,以模块化形式编写的程序可以存储在源代码文件处于膨胀状态的目录中,也可以存储在模块化 JAR 文件或 JMOD 文件中。JAR 文件是一种非常常见的格式,在 Java 中已经使用了很长一段时间,尽管 Java 9 在 jar 工具中加入了一些重要的更改。JMOD 是 JDK9 引入的一种新的模块打包格式。另一种格式,称为 JIMAGE 文件,也是一个新的介绍,以及带有 JDK9 的 jlink 工具。因此,简而言之,模块可以按如下格式存储,除了以未压缩的膨胀状态存储在目录结构中:

  • JAR 文件格式
  • JMOD 文件格式
  • JIMAGE 文件格式

JAR 文件格式

JAR(Java Archive)文件格式在Java中被用于将多个Java类文件、相关元数据和其他资源(如文本、图像等)的聚合内容打包成一个单一的工件,使用zip压缩格式,该工件基本上使用了带有.jar文件扩展名的压缩格式。JDK提供了jar工具实用程序,用于将条目注册到jar文件中。这是自Java 8及其前身版本以来用于分发所有Java库和框架的通用文件格式。

以 JAR 文件格式分发库有其自身的问题,通常称为 JAR-Hell,类似于 DLL-Hell。任何有经验的 Java 程序员都知道依赖 JAR 库之间版本不匹配的折磨。不可能使用最新的 JDK 重新编译每个 Java 库,因为市场上有许多第三方库,许多关键业务应用程序都依赖于这些库。使用最新版本的 JDK 升级每一个库几乎是不可能的。理想的情况是,每个库都为每个 JDK 版本维护一个单独的 JAR。这是一项几乎不可能完成的任务。

JDK9通过允许开发人员以不同的方式打包库来解决这个问题。JAR现在包含多个JDK版本的发布库,换句话说,多版本的JAR (multi-release jar MRJAR) JEP 238: Multi-Release JAR Files (openjdk.org)。它扩展了JAR文件格式,以允许类文件的多个特定于java发布的版本共存于单个归档文件中(JEP 238)。这意味着包含在多个版本的jar包中的库可以在不同的JDK版本中使用,前提是开发人员将相关的编译库版本添加到归档文件中。典型的JAR文件如下所示:

- jar-root
   - A.class
   - B.class
   - C.class
- META-INF
- MANIFEST.MF

multi-release jar (MRJAR)中,存档可以包含相同类的多个版本。

- jar-root
   - A.class
   - B.class
   - C.class
- META-INF
- MANIFEST.MF
- version
- 8
   - A.class
   - B.class
- 9
   - C.class
   - D.class

在不支持 MRJAR 的情况下,可以将 jar 视为常规 JAR。该类的较新版本优先于旧版本。

JDK9 附带的增强型 jar 工具可用于创建 MRJAR jar。该命令的简要用法如下:

用法: jar [OPTION...] [ [--release VERSION] [-C dir] files] ...

现在,假设我们想创建一个名为 org.mypackage1 的包(或 Java 9 及以后的模块)的 MRJAR,并将其存储在名为 mydir 的目录中。Ee 可以按如下方式执行:

jar --create --verbose --file mydir/org.mymodule1
-C org.mypackage1.jdk7/classes .
--release 8 -C  org.mypackage1.jdk8/classes .
--release 9 -C  org.mypackage1.jdk9/classes .

所有来自jdk7的类将被添加到MRJAR 根目录中,所有使用 version (–release) 选项指定的类将分别添加到 META-INF/versions/8META_INF/versions/9 中。

参考:有关此内容的更多详细信息,请参阅 JEP 238: Multi-Release JAR Files

JMOD 文件格式

根据文档,JMOD 支持聚合类文件、元数据和资源(如本机代码和其他不能存储在 JAR 文件中的内容)以外的文件。因此,JMOD 文件被设计为包含 JAR 文件无法包含的文件类型。但是,与可执行的 JAR 不同,JMOD 文件不能执行。这意味着 JMOD 中包含的这些文件只能在编译时或链接时使用,而不能在运行时使用。

......大多数开发任务(包括在模块路径上部署模块或将其发布到 Maven 存储库)将继续将模块打包到模块化 JAR 文件中。jmod 工具适用于具有本机库或其他配置文件的模块,或者您打算使用 jlink 工具链接到运行时映像的模块。

JMOD文件也基于ZIP格式,如JAR。JMOD 文件的扩展名是 .jmod。JDK9 附带的 jmod 工具的简要用法如下:

用法: jmod (create|extract|list|describe|hash) <选项> <jmod 文件>

假设我们想在 services 目录中创建一个 org.app.services.jar 的 JMOD 文件,并将其存储到 jmods 目录中。我们可以按如下方式进行操作:

jmod create --class-path services/org.app.services.jar jmods/org.app.services.jmod

解包jmod模块成jar:

解压jmods目录下的java.desktop.jmod模块到当前目录
jmod extract 'C:\Program Files\Eclipse Adoptium\jdk-17.0.2.8-hotspot\jmods\java.desktop.jmod'

使用jar工具在当前目录创建一个新的JAR文件

jar cf java.desktop.jar -C classes .

使用命令进行验证,将会成列出jar包中所有条目

jar tf .\java.desktop.jar

创建的 JMOD 文件可以与 jlink 工具一起使用,以创建自定义运行时映像或在应用程序的编译时使用它。在运行时使用它会标记一个名为 java.lang.module.ResolutionException 的异常。

JIMAGE 文件格式(java modules)

说明

JIMAGE 文件格式是 JVM 在运行时使用的优化文件格式。它存储模块、类和资源,并在 JDK 中为它们编制索引。JIMAGE 文件格式旨在模块化 JDK 并创建自定义运行时映像。它使JRE能够从一个整体结构转变为一个基于需求的最小运行时引擎。优点是显而易见的:时尚的 JRE 需要更少的启动时间,并且运行时占用的内存很小。这样创建的运行时映像以 JIMAGE 文件格式存储。因为它针对速度和空间进行了优化,所以它比 JAR 和 JMOD 文件更快。但是,与 JAR 和 JMOD 不同,JIMAGE 文件大多是 JDK 内部的,开发人员很少需要与它们交互。

可以使用 JDK9 附带的 jlink 工具创建自定义运行时映像。该命令的简要用法如下:

用法: jlink <选项> --module-path <模块路径> --add-modules <模块>[,<模块>...]

假设我们要创建一个数据库应用程序的 JIMAGE 文件,其中包含三个模块,例如模型模块、服务模块和视图模块,它们为客户端交互提供 GUI。我们可以按如下方式创建图像:

$  jlink --module-path jmods;/home/java9_projects/dbapp/jmods
--add-modules org.dbapp.model,org.dbapp.services,org.dbapp.view
--launcher runapp=org.dbapp.view/Main
--output dbapp

这将创建一个 JIMAGE 文件并将其放入 dbapp 目录中。应用程序的启动命令名称以 runapp 的形式提供。这将运行 org.dbapp.view.Main 类中包含的 main 函数。

Java 9 提供了另一个工具,称为 jimage。此工具可用于列出、提取、验证或获取有关 JIMAGE 条目的信息。它主要是一个用途简短的故障排除工具。

Usage: jimage <extract | info | list | verify> <options> jimage...

例如在当前文件夹下有一个名为modules的JIMAGE文件,可以用如下命令提取其信息

jimage extract ./modules

示例

在javafx的maven项目中可以用javafx-maven-plugin插件来生成带有JIMAGE 文件格式的jre(精简 根据项目 module-info.java文件来生成 )

例如javafx项目名为MediaPlayerFX

pom.xml中插件配置为

<plugin>
    <groupId>org.openjfx</groupId>
    <artifactId>javafx-maven-plugin</artifactId>
    <version>${javafx.maven.plugin.version}</version>
    <configuration>
        <jlinkImageName>MediaPlayerFX</jlinkImageName>
        <launcher>MediaPlayerFX</launcher>
        <mainClass>com.ita/com.ita.Main</mainClass>
    </configuration>
</plugin>

通过mvn javafx:jlink命令可以生成带有JIMAGE 文件格式的jre,其结构如下

MediaPlayerFX
├── bin
│   ├── MediaPlayerFX	-----------------(linux 项目启动脚本)
│   ├── MediaPlayerFX.bat -----------------(windows 下项目启动脚本)
│   .
│   ├── java.dll
│   ├── server
│   │   └── jvm.dll
├── conf
.
.
├── include
.
.
├── legal
.
├── lib
│   ├── classlist
.
│   ├── jvm.lib
│   ├── modules ------------------------>(JIMAGE 文件)
.
└── release


其中

MediaPlayerFX(linux 项目启动脚本)内容如下

#!/bin/sh
JLINK_VM_OPTIONS=
DIR=`dirname $0`
$DIR/java $JLINK_VM_OPTIONS -m com.ita/com.ita.Main "$@"

即在MediaPlayerFX文件夹执行

./java -m com.ita/com.ita.Main

即可启动项目

之所以能够找到 com.ita/com.ita.Main是因为解析了lib目录下的modules(JIMAGE 文件)

可以使用 ./java --list-modules 命令来查看一个jdk/jre下的 模块

在MediaPlayerFX目录下执行结果如下

./java --list-modules

com.ita@0.0.1
java.base@21
java.datatransfer@21
java.desktop@21
java.logging@21
java.net.http@21
java.prefs@21
java.scripting@21
java.sql@21
java.transaction.xa@21
java.xml@21
javafx.base
javafx.controls
javafx.fxml
javafx.graphics
javafx.media
jdk.internal.vm.ci@21
jdk.unsupported@21

其中@后数字为版本号

人生不作安期生,醉入东海骑长鲸