您的搜索操作未匹配到任何结果。
我们建议您尝试以下操作,以帮助您找到所需内容:
它们是什么以及如何使用它们
作者:Paul Deitel
Paul Deitel
在本文中,我将介绍 Java 9 Platform Module System (JPMS),它是自 Java 诞生以来极重要的新软件工程技术。模块化是 Jigsaw 项目 的成果,可帮助各级开发人员在构建、维护和改进软件系统(尤其是大型系统)时提高工作效率。
模块化在包之上提供更高层次的聚合。关键的新语言元素是模块,它是一组专属命名、可重用的相关包、资源(例如图像和 XML 文件)和一个模块描述符,用于指定
Java SE 平台自 1995 年推出以来,现大约有 1000 万名开发人员使用它来为物联网 (IoT) 和其他嵌入式设备等受资源限制的设备构建各种小型应用,到大规模业务关键型和任务关键型系统。市面上有大量的旧有代码,但到目前为止,Java 平台主要是单一的通用解决方案。多年来,业者针对模块化 Java 投入了各种努力,但没有一个被广泛使用,也没有一个可以用于模块化 Java 平台。
实现 Java SE 平台模块化是一项花了很多年且具有挑战性的努力。JSR 277:Java 模块系统当初于 2005 年针对 Java 7 推出,之后被 JSR 376:Java Platform Module System 取代,作为 Java 8 的目标,直到 Java 9 延迟到 2017 年 9 月推出之后,Java SE 平台在 Java 9 中进行了模块化处理。
每个模块必须明确地指出其依赖项。
根据 JSR 376,对 Java SE 平台进行模块化的主要目标是
JEP 200:模块化 JDK
JEP 201:模块化源代码
JEP 220:模块化运行时镜像
JEP 260:封装大多数内部 API
JEP 261:模块系统
JEP 275:JAVA 应用模块化打包
JEP 282:JAVA 连接器 JLINK
JSR 376:JAVA 平台模块系统
JSR 379:JAVA SE 9
表 1 Java 模块化 JEP 和 JSR
Java 9 的一个关键概念是将 JDK 切割成模块以支持各种配置。(参阅“JEP 200:对 JDK 进行模块化。”表 1 显示了与 Java 模块化有关的所有 JEP 和 JSR。)使用 JDK bin 文件夹中的 java 命令配搭 --list-modules
选项,如下所示:
java --list-modules
列出 JDK 的模块集,其中包括实施 Java SE 语言规范(以 java
开头的名称)、JavaFX 模块(以 javafx
开头的名称)、特定于 JDK 的模块(以 jdk
开头的名称)和特定于 Oracle 的模块(以 oracle
开头的名称)的标准模块。每个模块名称后都接着一个版本字符串 — @9
表示该模块属于 Java 9。
正如我们所提到的,模块必须提供一个模块描述符,也就是用于指定模块的依赖项、让其他模块使用的包等的元数据。模块描述符是一个经过编译的模块声明,定义于 module-info.java
档案中。每个模块声明以关键字 module
开头,紧接着一个专属的模块名称,以及括在括号中的模块主体内容,如下所示:
模块系统的关键动力是强封装。
module modulename {
}
模块声明的正文可以留空,也可以包含各种 module 指令,包括 requires
、exports
、provides…with
、uses
和 opens
(我们讨论的每个指令)。您稍后会看到,编译模块声明会创建模块描述符,存储在模块根文件夹中名为 module-info.class
的文件中。在这里,我们简要介绍每个模块指令。过后,我们将展示实际的模块声明。
关键字 exports
、module
、open
、opens
、provides
、requires
、uses
、with
以及后面介绍的 to
和 transitive
是受限关键字。这些仅是模块声明中的关键字,可用作代码中的标识符。
requires。requires
模块指令指定此模块依赖于另一个模块,此关系称为模块依赖关系。每个模块必须明确地指出其依赖项。当模块 A requires
模块 B 时,模块 A 称为读取模块 B,模块 B 则是给模块 A 读取。如需指定对其他模块的依赖性,请使用 requires
,如下所示:
requires modulename;
还有一个 requires static
指令,用于指示在编译时必需要有模块,在运行时则不是必须的。这称为可选相关项,但不会在本介绍中讨论。
requires transitive — 隐式可读性。指定对其他模块的依赖性并确保其他模块读取您的模块时也能读取这依赖性,所谓的隐式可读性,请使用 requires transitive
,
requires transitive modulename;
思考 java.desktop
模块声明中的以下指令:
requires transitive java.xml
;
在这种情况下,任何读取 java.desktop
的模块也会隐式读取 java.xml
。例如,如果 java.desktop
模块中的方法返回 java.xml
模块中的类型,则读取 java.desktop
的模块中的代码将依赖于 java.xml
。如果 java.desktop
模块声明中没有 requires transitive
指令,除非这些相关模块显式读取 java.xml
,否则这些模块将不会编译。
根据 JSR 379,Java SE 的标准模块必须在所有情况下(如此处所述)授予隐含的可读性。此外,虽然 Java SE 标准模块可能依赖于非标准模块,但是它不能授予隐含的可读性。这可确保代码仅依赖于 Java SE 标准模块,可以执行于任何 Java SE 的实作版本中。
exports 及 exports…to。exports
模块指令指定模块的一个包,其 public
类型(及其嵌套的 public
和 protected
类型)应可供所有其他模块的代码访问。通过 exports…to
指令,您可以在逗号分隔的列表中指定哪个模块或模块的代码可以访问导出的包,这就是所谓的限定导出。
uses。uses
模块指令指定此模块所使用的服务,使此模块成为服务使用者。service 是类的对象,用于实施接口或扩展 uses
指令中指定的 abstract
类。
provides…with。provides…with
模块指令指定模块提供服务实施,使模块成为服务提供者。指令的 provides
部分指定模块的 uses
指令指令中列出的接口或 abstract
类;指令的 with
部分指定 implements
接口或 extends
abstract
类的服务提供类的名称。
open、opens 及 opens…to。在 Java 9 之前,reflection 可用于了解包中的所有类型以及类型的所有成员,包括其 私有
成员,无论您是否允许此功能。因此,没有任何东西是真正封装的。
模块系统的关键优势是强封装。默认情况下,模块中的某个类型是无法让其他模块访问的,除非它是公开类型并且您导出它的包,您只需要公开您希望公开的包,在 Java 9 中,这也适用于 reflection。
只允许在运行时访问特定的包。open 模块指令的形式
opens package
表示特定 包 的 public
类型(及其嵌套的 public
和 protected
类型)只能在运行时可供其他模块中的代码访问。同样,指定包中的所有类型(以及所有类型的成员)都可通过 reflection 进行访问。
只允许在运行时让特定模组访问特定的包。opens…to
模块指令的形式
opens package to comma-separated-list-of-modules
表示特定包 的 public
类型(及其嵌套的 public
和 protected
类型)只能在运行时可供列出的模块中的代码访问。同样,指定包中的所有类型(以及所有类型的成员)都可通过 reflection 进行访问。
只允许在运行时能访问模块中的所有包。如果某模块中的所有包能在运行时并通过 reflection 让所有其他模块访问,您可以公开
整个模块,如下所示:
open module modulename {
// module directives
}
默认情况下,一个在运行时拥有反射访问权限的模块可以查看该包的 public
类型(及其嵌套的 public
和 protected
类型)。但是,其他模块中的代码可以访问公开的包的所有类型以及这些类型中的所有成员,包括通过 setAccessible
访问私有
成员,和先前的 Java 版本相同。
有关 setAccessible
和 reflection 的详细信息,请参阅 Oracle 文档。
Paul Deitel 是 Deitel & Associates 首席执行官兼首席技术官,毕业于麻省理工学院,拥有 35 年的计算经验。他拥有超过 22 年的 Java 编程经验,是 Java Champion 获得者。他和合著者 Harvey M. Deitel 博士同为全球畅销编程语言书籍的作者。Paul 在国际上为工业界、政府和学术界的客户提供 Java、Android、iOS、C#、C++、C 和互联网编程课程。
注:本文摘自 Java Magazine 2017 年 9 月/10 月刊。
注:为免疑义,本网页所用以下术语专指以下含义: