Tips
做一个终身学习的人。
在这章节中, 主要介绍以下内容:
模块可以以不同的格式打包,以便在编译时,链接时和运行时三个阶段中使用。 但并不是在所有阶段都支持这四种格式。 JDK 9 支持以下格式打包模块:
在 JDK 9 之前支持展开的目录和 JAR 格式。JDK 9 中的 JAR 格式已得到增强,以支持模块化 JAR 和多版本 JAR。 JDK 9 为封装模块引入了两种新格式:JMOD 格式和 JIMAGE 格式。 本节主要讨论 JAR 格式和 JMOD 格式的增强。
在前面的第三者介绍了如何在 jar 工具中使用新的选项来创建模块化的 JAR。 jar 工具还用于列出 JAR 文件中的条目,并提取和更新 JAR 文件的内容。 该 jar 工具在 JDK 9 之前已经支持这些操作,并且在 JDK 9 中没有任何新的操作。在本章中,将介绍添加到 JAR 格式的新功能,称为多版本 JAR。
作为一名经验丰富的 Java 开发人员,你必须使用 Java 类库 / 框架,例如 Spring 框架,Hibernate 等。您可能正在使用 Java 8,但这些类库可能仍然在 Java 6 或 Java 7 中使用。为什么类库开发人员不能使用最新版的 JDK 来使用新功能? 其中一个原因是不是所有的类库使用者都使用最新的 JDK。 更新类库以使用较新版本的 JDK 意味着强制所有类库用户迁移到较新的 JDK,这在实践中是不可能的。 维护和发布针对不同 JDK 的类库是打包代码时的另一个痛苦。 通常,你将会找到一个用于不同 JDK 的单独的库 JAR。 JDK 9 通过为类库开发人员提供一种打包类库代码的新方法来解决这个问题,使用单个 JAR 包含多个 JDK 的类库的相同版本。 这样的 JAR 被称为多版本 JAR。
多版本 JAR(MRJAR)包含与多个 JDK 版本相同版本的类库(提供相同的 API)。 也就是说,可以将类库作为可用于 JDK 8 和 JDK 9 的 MRJAR。MRJAR 中的代码将包含在 JDK 8 和 JDK 9 中编译的类文件。使用 JDK 9 编译的类可以利用 JDK 9 提供的 API,而使用 JDK 8 编译的类可以提供使用 JDK 8 编写的相同的类库 API。
MRJAR 扩展了 JAR 的已有的目录结构。 JAR 包含其所有内容所在的根目录。 它包含一个 META-INF 目录,用于存储有关 JAR 的元数据。 通常,JAR 包含包含其属性的 META-INF/MANIFEST.MF 文件。 典型的 JAR 中的条目如下所示:
- - jar-root
- - C1.class
- - C2.class
- - C3.class
- - C4.class
- - META-INF
- - MANIFEST.MF
JAR 包含四个 class 文件和一个 MANIFEST.MF 文件。 MRJAR 扩展了 META-INF 目录以存储特定于 JDK 版本的类。 META-INF 目录包含一个版本子目录,其中可能包含许多子目录,每个目录命名与 JDK 主要版本相同。 例如,对于特定于 JDK 9 的类,可能有 META-INF/versions/9 目录,对于 JDK 10 特定的类,可能有一个名为 META-INF/versions/10 的目录等。典型的 MRJAR 可能有以下条目:
- - jar-root
- - C1.class
- - C2.class
- - C3.class
- - C4.class
- - META-INF
- - MANIFEST.MF
- - versions
- - 9
- - C2.class
- - C5.class
- - 10
- - C1.class
- - C2.class
- - C6.class
如果该 MRJAR 在不支持 MRJAR 的环境中使用,则将被视为常规 JAR ——根目录中的内容将被使用,META-INF/version/9 和 META-INF/versions/10 目录下的类将被忽略。 因此,如果这个 MRJAR 与 JDK 8 一起使用,则只能使用四个类:C1,C2,C3 和 C4。
当在 JDK 9 中使用这个 MRJAR 时,有五个类可以执行:C1,C2,C3,C4 和 C5。 将使用 META-INF/versions/9 目录中的 C2 类,而不是根目录中的 C2 类。 在这种情况下,MRJAR 表示它具有 JDK 9 的 C2 类的较新版本,该版本覆盖了 JDK 8 或更早版本的根目录中的 C2 版本。 JDK 9 版本还添加了一个名为 C5 的新类。
同样,如果使用 JDK 10,MRJAR 会覆盖类 C1 和 C2 类,并且包含 JDK 版本 10 的名为 C6 的新类。
在单个 MRJAR 中定位多个 JDK 版本,MRJAR 中的搜索过程与常规 JAR 不同。 在 MRJAR 中搜索资源或类文件使用以下规则:
我们来看一下使用以前显示的 MRJAR 结构的例子。 假设程序正在寻找 C3.class,当前版本的 JDK 是 10,搜索将从 META-INF/versions/10 开始,其中找不到 C3.class。 在 META-INF/versions/9 中继续搜索,其中找不到 C3.class。 现在搜索继续在根目录中,最后找到 C3.class。
另一个例子,假设你想在 JDK 版本为 10 时找到 C2.class。搜索从 META-INF/versions/10 开始,其中找到并返回 C2.class。
另一个例子,假设你想在 JDK 版本为 9 时找到 C2.class。搜索从 META-INF/versions/9 开始,其中找到并返回 C2.class。
另一个例子,假设你想在 JDK 版本为 8 时找到 C2.class。没有名为 META-INF/versions/8 的 JDK 8 特定目录。 因此,搜索从根目录开始,找到并返回 C2.class。
Tips
在 JDK 9 中,处理 JAR 的所有工具(如 java,javac 和 javap)都被修改为使用多版本的 JAR。 处理 JAR 的 API 也已经更新,以处理多版本的 JAR。
当在特定的 JDK 版本中搜索资源或类文件后,我们已经知道 MRJAR 中目录的搜索顺序,很容易了解如何找到类和资源。 有关 JDK 版本特定目录内容的一些规则。 将在后面的章节中描述这些规则。 在本节中,将重点介绍创建 MRJAR。
要运行此示例,需要在计算机上安装 JDK 8 和 JDK 9。 如果没有 JDK 8,除 JDK 9 之外的任何其他 JDK 都可以。 对于除版本 8 以外的 JDK,将需要更改示例中的代码,因此代码将使用你本地版本的 JDK 进行编译。
使用 MRJAR 来存储应用程序的 JDK 8 和 JDK 9 版本。 该应用程序由以下两个类组成:
- com.jdojo.mrjar.Main
- com.jdojo.mrjar.TimeUtil
类创建一个
- Main
类的对象,并调用它的一个方法。
- TimeUtil
类可以用作运行应用程序的主类。
- Main
类包含一个
- TimeUtil
方法,它将
- getLocalDate(Instant now)
作为参数,并返回一个
- Instant
类来表示当前时区的时间。 JDK 9 已经为
- LocalDate
类添加了一个新方法,它被命名为
- LocalDate
。 我们将更新应用程序以使用 JDK 9 并利用这种新方法,并保留使用 JDK 8 中实现相同功能的 Time API 的旧应用程序。
- ofInstant(Instant instant, ZoneId zone)
源代码包含两个名为
和
- com.jdojo.mrjar.jdk8
的 NetBeans 项目,它们分别配置为使用 JDK 8 和 JDK 9。 在 NetBeans 中,需要将
- com.jdojo.mrjar.jdk9
目的源和库属性更改为 JDK 8,并将
- com.jdojo.mrjar.jdk8项
项目更改为 JDK 9。这些项目的源代码很简单。 可以在
- com.jdojo.mrjar.jdk9
类中创建一个静态方法的
- TimeUtil
方法。 在这里作为一个实例方法,所以你可以看到在输出(稍后讨论)哪个版本的实例化。 运行
- getLocalDate()
类时,会打印当前的本地日期,当你运行此示例时结果可能会有所不同。
- Main
下面包含使用 JDK 8 的
和
- TimeUtil
类的代码。
- Main
- // TimeUtil.java
- package com.jdojo.mrjar;
- import java.time.Instant;
- import java.time.LocalDate;
- import java.time.ZoneId;
- public class TimeUtil {
- public TimeUtil() {
- System.out.println("Creating JDK 8 version of TimeUtil...");
- }
- public LocalDate getLocalDate(Instant now) {
- return now.atZone(ZoneId.systemDefault())
- .toLocalDate();
- }
- }
- // Main.java
- package com.jdojo.mrjar;
- import java.time.Instant;
- import java.time.LocalDate;
- public class Main {
- public static void main(String[] args) {
- System.out.println("Inside JDK 8 version of Main.main()...");
- TimeUtil t = new TimeUtil();
- LocalDate ld = t.getLocalDate(Instant.now());
- System.out.println("Local Date: " + ld);
- }
- }
下面包含使用 JDK 9 的
和
- TimeUtil
类的代码模块声明为 com.jdojo.mrjar。
- Main
- // module-info.java
- module com.jdojo.mrjar {
- exports com.jdojo.mrjar;
- }
- // TimeUtil.java
- package com.jdojo.mrjar;
- import java.time.Instant;
- import java.time.LocalDate;
- import java.time.ZoneId;
- public class TimeUtil {
- public TimeUtil() {
- System.out.println("Creating JDK 9 version of TimeUtil...");
- }
- public LocalDate getLocalDate(Instant now) {
- return LocalDate.ofInstant(now, ZoneId.systemDefault());
- }
- }
- // Main.java
- package com.jdojo.mrjar;
- import java.time.Instant;
- import java.time.LocalDate;
- public class Main {
- public static void main(String[] args) {
- System.out.println("Inside JDK 9 version of Main.main()...");
- TimeUtil t = new TimeUtil();
- LocalDate ld = t.getLocalDate(Instant.now());
- System.out.println("Local Date: " + ld);
- }
- }
这个例子的目的不是单独运行这两个类,而是将它们全部包装在 MRJAR 中并运行它们。
JDK 9 中的 jar 工具已得到增强,以支持创建 MRJAR。 在 JDK 9 中,jar 工具接受一个新的选项,叫做
。 其语法如下:
- --release
- jar <options> --release N <other-options>
这里,N 是一个 JDK 主版本,如 JDK 9 中的 9。N 的值必须大于或等于 9。所有在
选项之后的所有文件将被添加到 MRJAR 的 META-INF/versions/N 目录下。
- --release N
以下命令创建名为 com.jdojo.mrjar.jar 的 MRJAR,并将其放在 C:\ Java9Revealed\mrjars 目录下,该目录是已经存在的目录:
- C:\Java9Revealed>jar --create --file mrjars\com.jdojo.mrjar.jar
- -C com.jdojo.mrjar.jdk8\build\classes .
- --release 9 -C com.jdojo.mrjar.jdk9\build\classes .
请注意在此命令中使用
选项。 来自 com.jdojo.mrjar.jdk9\ build\classes 目录的所有文件将被添加到 MRJAR 中的 META-INF/versions/9 目录中。 来自 com.jdojo.mrjar.jdk8\build\classes 目录的所有文件将被添加到 MRJAR 的根目录下。 MRJAR 中的条目将如下所示:
- --release 9
- - jar-root
- - com
- - jdojo
- - mrjar
- - Main.class
- - TimeUtil.class
- - META-INF
- - MANIFEST.MF
- - versions
- - 9
- - module-info.class
- - com
- - jdojo
- - mrjar
- - Main.class
- - TimeUtil.class
在创建 MRJAR 时,使用
选项在 jar 工具中非常有帮助。 该选项打印出许多有用的信息,帮助诊断错误。 以下是与以前相同的命令,但使用了
- --verbose
选项。 输出显示哪些文件被复制以及它们的位置:
- --verbose
- C:\Java9Revealed>jar --create --verbose --file mrjars\com.jdojo.mrjar.jar
- -C com.jdojo.mrjar.jdk8\build\classes .
- --release 9 -C com.jdojo.mrjar.jdk9\build\classes .
输出信息为:
- added manifest
- added module-info: META-INF/versions/9/module-info.class
- adding: com/(in = 0) (out= 0)(stored 0%)
- adding: com/jdojo/(in = 0) (out= 0)(stored 0%)
- adding: com/jdojo/mrjar/(in = 0) (out= 0)(stored 0%)
- adding: com/jdojo/mrjar/Main.class(in = 1100) (out= 592)(deflated 46%)
- adding: com/jdojo/mrjar/TimeUtil.class(in = 884) (out= 503)(deflated 43%)
- adding: META-INF/versions/9/(in = 0) (out= 0)(stored 0%)
- adding: META-INF/versions/9/.netbeans_automatic_build(in = 0) (out= 0)(stored 0%)
- adding: META-INF/versions/9/.netbeans_update_resources(in = 0) (out= 0)(stored 0%)
- adding: META-INF/versions/9/com/(in = 0) (out= 0)(stored 0%)
- adding: META-INF/versions/9/com/jdojo/(in = 0) (out= 0)(stored 0%)
- adding: META-INF/versions/9/com/jdojo/mrjar/(in = 0) (out= 0)(stored 0%)
- adding: META-INF/versions/9/com/jdojo/mrjar/Main.class(in = 1328) (out= 689)(deflated 48%)
- adding: META-INF/versions/9/com/jdojo/mrjar/TimeUtil.class(in = 814) (out= 470)(deflated 42%)
假设要为 MRJAR 创建 JDK 的版本 8,9 和 10。com.jdojo.mrjar.jdk10\build\classes 目录包含特定于 JDK 10 的类,则以下命令将执行该任务:
- C:\Java9Revealed>jar --create --file mrjars\com.jdojo.mrjar.jar
- -C com.jdojo.mrjar.jdk8\build\classes .
- --release 9 -C com.jdojo.mrjar.jdk9\build\classes .
- --release 10 -C com.jdojo.mrjar.jdk10\build\classes .
可以使用
选项验证 MRJAR 中的条目,如下所示:
- --list
- C:\Java9Revealed>jar --list --file mrjars\com.jdojo.mrjar.jar
输出结果为:
- META-INF/
- META-INF/MANIFEST.MF
- com/
- com/jdojo/
- com/jdojo/mrjar/
- com/jdojo/mrjar/Main.class
- com/jdojo/mrjar/TimeUtil.class
- META-INF/versions/9/
- META-INF/versions/9/com/
- META-INF/versions/9/com/jdojo/
- META-INF/versions/9/com/jdojo/mrjar/
- META-INF/versions/9/com/jdojo/mrjar/Main.class
- META-INF/versions/9/com/jdojo/mrjar/TimeUtil.class
- META-INF/versions/9/module-info.class
- META-INF/versions/10/
- META-INF/versions/10/com/
- META-INF/versions/10/com/jdojo/
- META-INF/versions/10/com/jdojo/mrjar/
- META-INF/versions/10/com/jdojo/mrjar/TimeUtil.class
假设有一个包含 JDK 8 的资源和类文件的 JAR,并且希望通过为 JDK 9 添加资源和类文件来更新 JAR 以使其成为 MRJAR。可以通过使用以下命令来更新 JAR 的内容:
。 以下命令创建仅具有 JDK 8 文件的 JAR:
- --update
- C:\Java9Revealed>jar --create --file mrjars\com.jdojo.mrjar.jar
- -C com.jdojo.mrjar.jdk8\build\classes .
以下命令更新 JAR 以使其成为 MRJAR:
- C:\Java9Revealed>jar --update --file mrjars\com.jdojo.mrjar.jar
- --release 9 -C com.jdojo.mrjar.jdk9\build\classes .
看看这个 MRJAR 的运作。 以下命令运行 com.jdojo.mrjar 包中的 Main 类,将 MRJAR 放在类路径上。 JDK 8 用于运行类。
- C:\Java9Revealed> c:\java8\bin\java -classpath mrjars\com.jdojo.mrjar.jar com.jdojo.mrjar.Main
输出结果为:
- Inside JDK 8 version of Main.main()...
- Creating JDK 8 version of TimeUtil...
- Local Date: 2017-06-27
输出显示,从 MRJAR 的根目录使用了两个类
和
- Main
类,因为 JDK 8 不支持 MRJAR。 以下命令使用模块路径运行相同的类。 在 JDK 9 中用于运行命令:
- TimeUtil
- C:\Java9Revealed> c:\java9\bin\java --module-path mrjars\com.jdojo.mrjar.jar --module com.jdojo.mrjar/com.jdojo.mrjar.Main
输出结果为:
- Inside JDK 9 version of Main.main()...
- Creating JDK 9 version of TimeUtil...
- Local Date: 2017-06-27
输出显示,从 MRJAR 的 META-INF/versions/9 目录中使用了两个类
和
- Main
,因为 JDK 9 支持 MRJAR,MRJAR 具有 JDK 9 特有的这些类的版本。
- TimeUtil
让我们给这个 MRJAR 一点点变化。 创建具有相同内容的 MRJAR,但在 META-INF/versions/9 目录中没有 Main.class 文件。 在现实世界的场景中,只有
类在应用程序的 JDK 9 版本中发生变化,因此不需要为 JDK 9 打包 Main 类。JDK 8 的
- TimeUtil
类也可用于 JDK 9。 以下命令打包我们上次执行的所有操作,除了 JDK 9 的 Main 类之外。生成的 MRJAR 命名为 com.jdojo.mrjar2.jar。
- Main
- C:\Java9Revealed>jar --create --verbose --file mrjars\com.jdojo.mrjar2.jar
- -C com.jdojo.mrjar.jdk8\build\classes .
- --release 9
- -C com.jdojo.mrjar.jdk9\build\classes module-info.class
- -C com.jdojo.mrjar.jdk9\build\classes com\jdojo\mrjar\TimeUtil.class
可以使用以下命令验证新 MRJAR 的内容:
- C:\Java9Revealed>jar --list --file mrjars\com.jdojo.mrjar2.jar
输出的结果为:
- META-INF/
- META-INF/MANIFEST.MF
- META-INF/versions/9/module-info.class
- com/
- com/jdojo/
- com/jdojo/mrjar/
- com/jdojo/mrjar/Main.class
- com/jdojo/mrjar/TimeUtil.class
- META-INF/versions/9/com/jdojo/mrjar/TimeUtil.class
如果在 JDK 8 上运行 Main 类,将获得与以前相同的输出。 但是,在 JDK 9 上运行它会给你一个不同的输出:
命令行命令:
- C:\Java9Revealed>c:\java9\bin\java --module-path mrjars\com.jdojo.mrjar2.jar --module com.jdojo.mrjar/com.jdojo.mrjar.Main
输出结果为:
- Inside JDK 8 version of Main.main()...
- Creating JDK 9 version of TimeUtil...
- Local Date: 2017-06-27
输出显示
类是在 JAR 根目录使用的,而
- Main
类使用的是 META-INF/versions/9 目录下的。
- TimeUtil
创建多版本 JAR 时,需要遵循一些规则。 如果你犯了一个错误,jar 工具会打印错误信息。 有时,错误信息不直观。最好使用
选项运行 jar 工具来获取有关错误的更多详细信息。
- --verbose
大多数规则基于一个事实:MRJAR 包含用于多个 JDK 平台的类库(或应用程序)的同一个版本的 API。 例如,有一个名为 jdojo-lib-1.0.jar 的 MRJAR,可能包含名为 jdojo-lib 的库的 1.0 版本,该库可能使用 JDK 8 和 JDK 9 中的 API。这意味着该 MRJAR 应该提供一份相同的 API(在公共类型及其公共成员方面),用在 JDK 8 的类路径上,或 JDK 9 的类路径或模块路径上。 如果 MRJAR 在 JDK 8 和 JDK 9 上提供不同的 API,那么这不是有效的 MRJAR。
MRJAR 可以是模块化 JAR,在这种情况下,它可以在根目录中包含一个模块描述符 module-info.class,在一个或多个版本化目录中,或两者的组合。 版本化描述符必须与根模块描述符相同,但有一些例外:
和
- java.*
开头模块的非传递性
- jdk.*
语句。
- requires
语句。
- requires
语句。
- uses
这些规则是基于实现细节的变化是允许的,但是 API 本身并不是这样。 允许对非 JDK 模块的
语句的更改被认为是 API 中的一个变化 —— 它要求你为不同版本的 JDK 拥有不同的用户自定义的模块。 这就是为什么这不允许的原因。
- requires
模块化 MRJAR 不需要在根目录中有一个模块描述符。 这是我们在上一节的例子中所说的。 我们在根目录中没有模块描述符,但在 META-INF/versions/9 目录中有一个描述符。 这种安排可以在一个 MRJAR 中具有用于 JDK 8 的非模块化代码和用于 JDK 9 的模块代码。
如果在版本目录(在根目录中不存在)中添加新的公共类型,则在创建 MRJAR 时收到错误。假设在我们的示例中,为 JDK 9 版本添加一个名为
的公共类。如果
- Test
类在 com.jdojo.mrjar 包中,它将被该模块导出,并且可用于 MRJAR 之外的代码。请注意,根目录不包含
- Test
类,因此此 MRJAR 为 JDK 8 和 JDK 9 提供不同的公共 API。在这种情况下,当您创建 MRJAR 时, 在 JDK 9 的 com.jdojo.mrjar 包中添加公共
- Test
类将会生成错误。
- Test
继续使用相同的示例,假设将
类添加到 JDK 9 的 com.jdojo.test 包中。请注意,该模块不导出此包。当在模块路径上使用此 MRJAR 时,
- Test
类将无法访问外部代码。在这个意义上,这个 MRJAR 为 JDK 8 和 JDK 9 提供了相同的公共 API。但是,有一个隐情!你也可以将此 MRJAR 放在 JDK 9 中的类路径上,在这种情况下,外部代码可以访问
- Test
类,但这是对模块化封装的一种违反,以及违反 MRJAR 应该提供相同公共 API 的规则跨越不同的 JDK 版本。因此,不允许在 MRJAR 中为模块添加公共类型到未导出的包。如果尝试这样做,将收到类似于以下内容的错误消息:
- Test
- entry: META-INF/versions/9/com/jdojo/test/Test.class, contains a new public class not found in base entries
- invalid multi-release jar file mrjars\com.jdojo.mrjar.jar deleted
有时,需要为同一个类库添加更多类型来支持较新版本的 JDK。 必须添加这些类型才能支持较新的实现。 可以通过将包级别的私有类型添加到 MRJAR 中的版本化目录中来实现。 在这个例子中,如果使类非公开类型,可以添加 JDK 9 的
类。
- Test
引导加载器不支持多版本 JAR,例如,使用
选项指定 MRJAR。支持这个将使得很少需要的功能的引导加载器实现变得复杂化。
- -Xbootclasspath / a
MRJAR 应该在版本目录中包含相同文件的不同版本。 如果资源或类文件在不同的平台版本中是相同的,那么这样一个文件应该被添加到根目录。 目前,如果 jar 工具在具有相同内容的多版本目录中看到相同的条目,则会发出警告。
让我们看看这个规则的实际效果。 将 com.jdojo.mrjar.jdk9\build \ 目录的内容复制到 com.jdojo.mrjar.jdk10\build\classes 目录,因此这两个目录具有相同的内容。 运行以下命令创建一个包含 JDK 版本 8,9 和 10 的代码的 MRJAR。请注意,版本化目录 9 和 10 中的文件将是相同的。 执行命令如下。
- C:\Java9Revealed>jar --create --file mrjars\com.jdojo.mrjar.jar
- -C com.jdojo.mrjar.jdk8\build\classes .
- --release 9 -C com.jdojo.mrjar.jdk9\build\classes .
- --release 10 -C com.jdojo.mrjar.jdk10\build\classes .
报出的警告信息为:
- Warning: entry META-INF/versions/9/com/jdojo/mrjar/Main.class contains a class that
- is identical to an entry already in the jar
- Warning: entry META-INF/versions/9/com/jdojo/mrjar/TimeUtil.class contains a class that
- is identical to an entry already in the jar
在 MRJAR 之前,JAR 中的所有资源都存在于根目录中。 当从类加载器(
)请求资源时,返回的 URL 类似于以下内容:
- ClassLoader.getResource("com/jdojo /mrjar/TimeUtil.class")
- jar: file: /C:/Java9Revealed / mrjars / com.jdojo.mrjar.jar ! com / jdojo / mrjar / TimeUtil.class With MRJARs,
- a resource may be returned from the root directory or from a versioned directory.If you are looking
- for the TimeUtil.class file on JDK 9,
- the URL will be as follows: jar: file: /C:/Java9Revealed / mrjars / com.jdojo.mrjar.jar ! /META-INF/versions / 9 / com / jdojo / mrjar / TimeUtil.class
如果你现有的代码期望特定格式的资源的 jar 文件的 URL,或者手工编写了了一个 URL,当与 MRJAR 一起工作时,可能会获得意想不到的结果。如果正在使用 MRJAR 重新打包 JAR,则需要再次查看代码并将其更改,能够在 MRJAR 下工作。
MRJAR 在其 MANIFEST.MF 文件中包含一个特殊属性:
- Multi-Release: true
属性在使用 jar 工具创建 MRJAR 时添加。 如果此属性的值为 true,则表示 JAR 是多版本 JAR。 如果其值为 false 或属性缺失,则不是多版本 JAR。
- Multi-Release
名为
的新常量已添加到
- MULTI_RELEASE
类(位于 java.util.jar 包中),以表示 manifest 文件中的新属性
- Attributes.Name
。 因此,
- Multi-Release
常量表示 Java 代码中
- Attributes.Name.MULTI_RELEASE
属性的值。
- Multi-Release
JDK 9 引入了一种称为 JMOD 的新格式来封装模块。 JMOD 文件旨在处理比 JAR 文件更多的内容类型。 JMOD 文件可以打包本地代码,配置文件,本地命令和其他类型的数据。 目前,JMOD 格式基于 ZIP 格式,将在将来会发生变化。 JDK 9 模块以 JMOD 格式打包,可以在编译时和链接时使用。 运行时不支持 JMOD 格式。 可以在 JDK_HOME\jmods 目录中找到它们,其中 JDK_HOME 是安装 JDK 9 的目录。可以使用 JMOD 格式打包自己的模块。 JMOD 格式的文件具有. jmod 扩展名。 例如,名为 java.base 的平台模块已打包在 java.base.jmod 文件中。
JMOD 文件可以包含本地代码,这在运行时提取和链接有点棘手。 这就是为什么 JMOD 文件在编译时和链接时都被支持,运行时却不可以。
JDK 9 附带了一个名为 jmod 的新工具。 它位于 JDK_HOME\bin 目录中。 它可以用于创建一个 JMOD 文件,列出一个 JMOD 文件的内容,打印一个模块的描述,并记录使用的模块的哈希值。 使用 jmod 工具的一般语法如下:
- jmod <subcommand> <options> <jmod-file>
其中在 jmod 命令中至少包含以下一个子命令:
list 和 describe 子命令不接受任何选项。
是要创建的 JMOD 文件或要描述的现有 JMOD 文件。 下面包含该工具支持的选项列表。
- <jmod-file>
选项 | 描述 |
---|---|
|
指定可以找到要打包的类的类路径。可以是包含应用程序类的 JAR 文件或目录的路径列表。 中的内容将被复制到 JMOD 文件。 |
|
指定包含本地命令的目录列表,这些目录需要复制到 JMOD 文件中。 |
|
指定包含要复制到 JMOD 文件的用户可编辑配置文件的目录列表。 |
|
指定提取指定的 JMOD 文件的内容的目标目录。 |
|
如果使用此选项创建 JMOD 文件,JMOD 文件中包含的模块将从默认的根模块中排除。 要解决此类模块,必须使用 命令行选项将其添加到默认的根模块中。 |
|
模块的哈希值。 使用此选项可以计算和打印哈希值,但不将它们记录在 JMOD 文件中。 |
|
排除与提供的逗号分隔模式列表匹配的文件,每个元素使用以下格式之一:, ,或 。 |
|
计算和记录哈希值,以将打包的模块与符合给定的 的模块进行绑定,并直接或间接依赖于它。 在正在创建的 JMOD 文件中记录哈希值,或者在使用 jmod 哈希命令指定的模块路径上的 JMOD 文件或模块化 JAR。 |
|
打印使用说明和 jmod 命令的所有选项列表。 |
|
将路径列表指定为 ,将要复制到 JMOD 文件的本机代码的头文件放置于此。 |
|
打印 jmod 工具支持的其他选项的帮助信息。 |
|
指定要复制到 JMOD 文件的合法声明的位置。 |
|
指定包含要复制到 JMOD 文件的本地类库的目录列表。 |
|
指定要用于运行应用程序的主类名称。 |
|
指定手册主页的位置。 |
|
指定要记录在 module-info.class 文件中的模块版本。 |
|
指定找到散列模块的模块路径。 |
|
指定要记录在 module-info.class 文件中的操作系统体系结构。 |
|
指定要记录在 module-info.class 文件中的操作系统名称。 |
|
打印 jmod 工具的版本。 |
|
指定一个 jmod 工具的提示,如果一个模块被解决,发出一个警告。的值可能是三种: , , 或 。 |
|
从指定的文件中读取选项。 |
以下部分将详细介绍如何使用 jmod 命令。 本章中使用的所有命令均输入一行。 有时候,为了显示更加清晰,将它们显示在多行上。
你可以使用 jmod 工具命令的子命令
来创建一个 jmod 文件。一个 jmod 文件的内容是一个模块的内容。假设下列目录和文件已经存在:
- create
以下命令在 C:\ Java9Revealed\jmods 目录中创建一个 com.jdojo.prime.jmod 文件。 JMOD 文件的内容来自 com.jdojo.prime.jar 文件。
- C:\Java9Revealed>jmod create --class-path lib\com.jdojo.prime.jar
- jmods\com.jdojo.prime.jmod
通常,JMOD 文件的内容来自包含模块编译代码的一系列目录。 以下命令创建一个 com.jdojo.prime.jmod 文件。它的内容来自一个 mods\com.jdojo.prime 目录。 该命令使用
选项来设置将记录在 com.jdojo.prime\build\classes 目录中的 module-info.class 文件中的模块版本。 确保删除在上一步中创建的 JMOD 文件。
- --module-version
- C:\Java9Revealed>jmod create --module-version 1.0
- --class-path com.jdojo.prime\build\classes jmods\com.jdojo.prime.jmod
你可以用这个 JMOD 文件做什么? 可以将其放在模块路径上,以便在编译时使用它。 可以使用它与 jlink 工具来创建可用于运行应用程序的自定义运行时映像。 回想一下,你不能在运行时使用它。 如果尝试在运行时使用 JMOD 文件放在模块路径上,将收到以下错误:
- Error occurred during initialization of VM
- java.lang.module.ResolutionException: JMOD files not supported: jmods\com.jdojo.prime.jmod
可以使用
子命令提取 JMOD 文件的内容。 以下命令将 jmods\com.jdojo.prime.jmod 文件的内容提取到名为 extract 的目录中。
- extract
- C:\Java9Revealed>jmod extract --dir extracted jmods\com.jdojo.prime.jmod
如果没有
选项,则 JMOD 文件的内容直接提取到当前目录下。
- --dir
可以使用
子命令与 jmod 工具打印 JMOD 文件中所有条目的名称。 以下命令列出了在上一节中创建的 com.jdojo.prime.jmod 文件的内容:
- list
- C:\Java9Revealed>jmod list jmods\com.jdojo.prime.jmod
以下命令列出了 java.base.jmod 的 JMOD 文件发布的 java.base 模块的内容。 该命令假设已经在 C:\java9 目录中安装了 JDK 9。 输出超过 120 页。 下面显示的是部分输出。 请注意,JMOD 文件内部将不同类型的内容存储在不同的目录中。
- C:\Java9Revealed>jmod list C:\java9\jmods\java.base.jmod
输出结果为(部分内容):
- classes/module-info.class
- classes/java/nio/file/WatchEvent.class
- classes/java/nio/file/WatchKey.class
- bin/java.exe
- bin/javaw.exe
- native/amd64/jvm.cfg
- native/java.dll
- conf/net.properties
- conf/security/java.policy
- conf/security/java.security
- ...
可以使用
子命令与 jmod 工具来描述 JMOD 文件中包含的模块。 以下命令描述 com.jdojo.prime.jmod 文件中包含的模块:
- describe
- C:\Java9Revealed>jmod describe jmods\com.jdojo.prime.jmod
输出结果为:
- com.jdojo.prime@1.0
- requires mandated java.base
- uses com.jdojo.prime.PrimeChecker
- exports com.jdojo.prime
可以使用此命令来描述平台模块。 以下命令描述了 java.sql.jmod 中包含的模块,假设已在 C:\java9 目录中安装了 JDK 9:
- C:\Java9Revealed>jmod describe C:\java9\jmods\java.sql.jmod
- java.sql@9-ea
输出结果为:
- requires mandated java.base
- requires transitive java.logging
- requires transitive java.xml
- uses java.sql.Driver
- exports java.sql
- exports javax.sql
- exports javax.transaction.xa
- operating-system-name Windows
- operating-system-architecture amd64
可以使用 jmod 工具的
子命令记录其他模块的哈希值,包含在一个 jmod 文件模块的 module-info.class 文件中。哈希值将用于以后的依赖性验证。假设你在四个 jmod 文件中有四个模块:
- hash
假设希望将这些模块提供给给客户,并确保模块代码保持不变。可以通过为这四个模块记录哈希值来实现。让我们看看如何做到这一点。
如果要计算其他模块的哈希值,首先 jmod 能够找到这些模块。 你需要使用
选项在模块路径中,以便找到其他模块。 同时,也需要使用
- --module-path
选项来指定要使用的哈希值记录模块的模式列表。
- --hash-modules
Tips
当你把一个模块打包成一个 JAR 格式时,可以在 jar 命令中使用和
- --hash-modules
来记录依赖模块的哈希值。
- --module-path
使用以下四个命令为四个模块创建 jmod 文件。在创建 com.jdojo.prime.client.jmod 时使用了
选项。 如果在运行这些命令时,给你一个 "file already exists" 的错误,从 jmods 目录删除现有的 jmod 文件并重新运行命令。
- --main-class
- C:\Java9Revealed>jmod create --module-version 1.0
- --class-path com.jdojo.prime\build\classes jmods\com.jdojo.prime.jmod
- C:\Java9Revealed>jmod create --module-version 1.0
- --class-path com.jdojo.prime.generic\build\classes
- jmods\com.jdojo.prime.generic.jmod
- C:\Java9Revealed>jmod create --module-version 1.0
- --class-path com.jdojo.prime.faster\build\classes
- jmods\com.jdojo.prime.faster.jmod
- C:\Java9Revealed>jmod create --main-class com.jdojo.prime.client.Main
- --module-version 1.0
- --class-path com.jdojo.prime.client\build\classes
- jmods\com.jdojo.prime.client.jmod
现在你准备好记录名字以 com.jdojo.prime 的模块的哈希值。在 com.jdojo.prime 模块中使用下面的命令:
- C:\Java9Revealed>jmod hash --module-path jmods
- --hash-modules com.jdojo.prime.? jmods\com.jdojo.prime.jmod
让我们看看记录在 com.jdojo.prime 模块的哈希值。下面命令打印记录有哈希值的模块描述:
- C:\Java9Revealed>jmod describe jmods\com.jdojo.prime.jmod
输出结果为:
- com.jdojo.prime@1.0
- requires mandated java.base
- uses com.jdojo.prime.PrimeChecker
- exports com.jdojo.prime
- hashes com.jdojo.prime.client SHA-256 2ffb0d4413501e389d6712450bd138bbe82ca8abeb4e8b5d29b0c307d90a2e91
- hashes com.jdojo.prime.faster SHA-256 687e07c429080c48bed89a649dca20fa26dc28fab88a4905f1b5070560622a0c
- hashes com.jdojo.prime.generic SHA-256 f24556ef69c4345ad7a8e5e59d31ea2d52c8749714ede0c0dedf128255450708
当你使用
子命令创建一个新的 jmod 文件时,也可以记录其他模块的哈希值。假设三个模块
- create
,
- com.jdojo.prime.generic
,和
- com.jdojo.prime.faster
存在于模块的路径,你可以使用下面的命令来创建 com.jdojo.prime.jmod 文件同时也记录了其他三个模块的哈希值:
- com.jdojo.prime.client
- C:\Java9Revealed>jmod create --module-version 1.0
- --module-path jmods
- --hash-modules com.jdojo.prime.?
- --class-path com.jdojo.prime\build\classes jmods\com.jdojo.prime.jmod
可以使用
选项运行哈希过程的 JMOD 文件,其中将打印哈希值,但不会记录。 此选项有助于确保所有设置是正确的,而无需创建 JMOD 文件。 以下的命令顺序将会引导你完成整个过程。 首先,删除在上一步中创建的 jmods\com.jdojo.prime.jmod 文件。
- --dry-run
以下命令创建 jmods\com.jdojo.prime.jmod 文件,而不会为其他模块记录哈希值:
- C:\Java9Revealed>jmod create --module-version 1.0
- --module-path jmods
- --class-path com.jdojo.prime\build\classes jmods\com.jdojo.prime.jmod
以下命令运行
子命令。 它计算并打印其他模块的哈希值,与
- hash --dry-run
选项中指定的正则表达式匹配。 jmods\com.jdojo.prime.jmod 文件中不会记录哈希值。
- --hash-modules
- C:\Java9Revealed>jmod hash --dry-run --module-path jmods
- --hash-modules com.jdojo.prime.? jmods\com.jdojo.prime.jmod
输出结果为:
- Dry run:
- com.jdojo.prime
- hashes com.jdojo.prime.client SHA-256 2ffb0d4413501e389d6712450bd138bbe82ca8abeb4e8b5d29b0c307d90a2e91
- hashes com.jdojo.prime.faster SHA-256 687e07c429080c48bed89a649dca20fa26dc28fab88a4905f1b5070560622a0c
- hashes com.jdojo.prime.generic SHA-256 f24556ef69c4345ad7a8e5e59d31ea2d52c8749714ede0c0dedf128255450708
以下命令验证上一个命令在 JMOD 文件中没有记录哈任何希值:
- C:\Java9Revealed>jmod describe jmods\com.jdojo.prime.jmod
输出结果为:
- com.jdojo.prime@1.0
- requires mandated java.base
- uses com.jdojo.prime.PrimeChecker
- exports com.jdojo.prime
JDK 9 支持四种格式来打包模块:展开的目录,JAR 文件,JMOD 文件和 JIMAGE 文件。 JAR 格式在 JDK 9 中得到了增强,以支持模块化 JAR 和多版本 JAR。多版本 JAR 允许你打包相同版本的类库或面向 JDK 不同版本的应用程序。例如,多版本 JAR 可能包含包含 JDK 8 和 JDK 9 代码的库版本为 1.2 的代码。当在 JDK 8 上使用多版本 JAR 时,将使用 JDK 8 版本的库代码。当它在 JDK 9 上使用时,将使用 JDK 9 版本的库代码。特定于 JDK 版本 N 的文件存储在多版本 JAR 的 META-INF\versions\N 目录中。所有 JDK 版本通用的文件都存储在根目录中。对于不支持多版本 JAR 的环境,此类 JAR 将被视为常规 JAR。在多版本 JAR 中,文件的搜索顺序是不同的,所有从当前平台的主版本开始的所有版本化目录都在根目录之前进行搜索。
JMOD 文件旨在处理比 JAR 文件更多的内容类型。它们可以打包本地代码,配置文件,本地命令和其他类型的数据。目前,JMOD 格式基于 ZIP 格式,将在将来会发生变化。 JDK 9 模块以 JMOD 格式打包,可以在编译时和链接时使用。运行时不支持 JMOD 格式。可以使用 jmod 工具来处理 JMOD 文件。
来源: http://www.cnblogs.com/IcanFixIt/p/7095839.html