46

在 stackoverflow 上多次提到自动模块,但我找不到自动模块的完整、简洁和自给自足的定义。

那么,什么是自动模块?它是否导出所有包?它会打开所有包裹吗?它是否读取所有其他模块?

4

3 回答 3

58

我首先回答您的实际问题(“什么是自动模块?”),但我也解释了它们的用途。很难理解为什么自动模块在没有这些信息的情况下会表现得如此。

什么是自动模块?

模块系统从它在模块路径上找到的每个 JAR 创建一个模块。对于模块化 JAR(即具有模块描述符的 JAR)来说,它们很简单,因为它们定义了模块的属性(名称、要求、导出)。对于普通的 JAR(没有模块描述符),这种方法不起作用,那么模块系统应该做什么呢?它会自动创建一个模块 - 可以说是一个自动模块- 并对三个属性进行最安全的猜测。

姓名

派生名称是一个两步过程:

  • 如果 JARAutomatic-Module-Name在其清单中定义了标头,则它定义了模块的名称
  • 否则,JAR 文件名用于确定名称

第二种方法本质上是不稳定的,因此不应该发布依赖于这种自动模块的模块。Maven 对此提出警告。

需要

由于一个普通的 JAR 不表达 requires 子句,模块系统允许自动模块读取所有其他模块,使其进入可读性图(也称为模块图)。与显式模块不同,自动模块还读取未命名模块,其中包含从类路径加载的所有内容。这个看似微不足道的细节实际上非常重要(见下文)。

不过,自动模块还有一些可读性的怪癖:

  • 一旦第一个自动模块得到解决,所有其他模块也得到解决。这意味着一旦模块路径上的单个普通 JAR 被另一个模块引用,所有普通 JAR 都会作为自动模块加载。
  • 自动模块意味着所有其他自动模块的可读性,这意味着读取其中一个模块的模块会读取所有其他模块。

总而言之,这可能会产生不幸的影响,即依赖于几个普通 JAR 的显式模块(即非自动模块)可以只需要其中一个(只要其他模块最终也在模块路径上) .

出口/打开

由于 JAR 不包含哪些包被视为公共 API 以及哪些不是公共 API 的信息,因此模块系统会导出所有包并打开它们以进行深度反射。

更多的

模块系统还扫描META-INF/services并让自动模块提供其中命名的服务。假定允许自动模块使用所有服务。

最后,Main-Classmanifest 条目也被处理,因此定义一个的普通 JAR 可以像使用jar工具设置主类的自动模块一样启动(即java --module-path my-app.jar --module my.app)。

合适的模块

创建自动模块后,它被视为任何其他模块。这明确包括模块系统检查任何其他模块,例如拆分包。

什么是自动模块?

引入模块的原因之一是使编译和启动应用程序更可靠,并更快地发现错误,这可以通过类路径实现。其中一个关键方面是requires条款。

为了保持它们的可靠性,模块声明除了命名模块之外没有其他方法,它排除了从类路径加载的所有内容。如果故事到此结束,模块化 JAR 只能依赖于其他模块化 JAR,这将迫使生态系统自下而上模块化。

但是,这是不可接受的,因此引入了自动模块作为模块化 JAR 依赖非模块化 JAR 的一种方式,您需要做的就是将普通 JAR 放在模块路径上,并通过模块系统的名称来要求它给它。

有趣的是,因为自动模块读取未命名的模块,所以将其依赖关系留在类路径上是可行的(我通常建议这样做)。这样,自动模块充当了从模块到类路径的桥梁。

您的模块可以位于一侧,需要将它们的直接依赖项作为自动模块,而间接依赖项可以保留在另一侧。每次您的依赖项变成显式模块时,它都会将桥留在模块化一侧,并将其直接依赖项作为自动模块绘制到桥上。

于 2017-10-14T09:07:45.390 回答
9

自动模块是隐式定义的命名模块,因为它没有模块声明。相比之下,普通的命名模块是通过模块声明显式定义的;我们以后将它们称为显式模块

使用这些的主要好处是它们允许您在编译或运行时将工件视为模块,而无需等待将其迁移到模块化结构。

如果自动模块的主清单条目中具有Automatic-Module-Name属性,则自动模块的模块名称源自用于包含工件的 JAR 文件。模块名称由ModuleFinder.


由于自动模块没有模块声明这一事实,实际上不可能告诉它读取、打开或导出的所有模块或包是什么。

➜ 因此,由于自动模块中的包没有明确的导出/打开,描述为 -

.. 没有实用的方法来判断自动模块中的哪些包是供其他模块使用的,或者仍然在类路径上的类。因此,自动模块中的每个包都被视为已导出,即使它实际上可能仅用于内部使用。

➜ 进一步引用链接 -

...没有实际的方法可以提前告诉自动模块可能依赖于哪些其他模块。因此,在解析模块图后,自动模块会读取所有其他命名模块,无论是自动的还是显式的。

建议之一- 自动模块提供传统级别的封装:所有包都对深度反射访问开放,为普通编译时和运行时对其公共类型的访问导出。


➜ 此外,一个自动模块

赋予所有其他自动模块隐含的可读性

由于这个原因,在一个模块中使用多个自动模块时,它的

..无法确定自动模块(xyz)中的导出包之一是否包含其签名引用其他自动模块(abc)中定义的类型的类型。

于 2017-10-14T07:17:53.293 回答
5

(您似乎对缺少完整的定义是正确的,因为我在语言或 JVM 规范中没有找到它)

java.lang.moduleJavadocs 尽管在包类中提供了大量的正式定义。

来自https://docs.oracle.com/javase/9 ​​/docs/api/java/lang/module/ModuleFinder.html#automatic-modules 的部分引用:

module-info.class顶级目录中没有 的 JAR 文件定义了一个自动模块,如下所示:

  • 如果 JAR 文件在其主清单中具有属性“ Automatic-Module-Name”,则其值为模块名称。模块名称是从 JAR 文件的名称派生而来的。

...

并来自https://docs.oracle.com/javase/9​​/docs/api/java/lang/module/ModuleDescriptor.html

自动模块的模块描述符不声明任何依赖项(除了对 的强制依赖项java.base),也不声明任何导出或打开的包。自动模块在解析期间接受特殊处理,以便它们读取配置中的所有其他模块。当一个自动模块在 Java 虚拟机中实例化时,它会读取每个未命名的模块,并被视为所有包都已导出和打开。

编辑(2021):

Java 语言规范中关于模块依赖的这一部分有以下有趣的注释:

Java SE 平台区分显式声明的命名模块(即使用模块声明)和隐式声明的命名模块(即自动模块)。但是,Java 编程语言并没有表现出这种区别:require 指令引用命名模块,而不考虑它们是显式声明还是隐式声明。

虽然自动模块便于迁移,但它们并不可靠,因为它们的名称和导出的包可能会在作者将它们转换为显式声明的模块时发生变化。如果 requires 指令引用自动模块,则鼓励 Java 编译器发出警告。如果传递修饰符出现在指令中,建议使用特别强烈的警告。

于 2017-10-14T07:39:16.823 回答