Module java.base

Package java.lang.module


package java.lang.module
用于支持模块描述符和通过解析和服务绑定创建模块配置的类。

除非另有说明,否则将null参数传递给此包中任何类或接口的构造函数或方法将导致抛出NullPointerException。此外,调用具有包含null元素的数组或集合的方法将导致NullPointerException,除非另有说明。

模块解析

解析是计算模块彼此之间依赖关系的过程。该过程发生在编译时和运行时。

解析是一个两步过程。第一步递归枚举一组根模块的'requires'指令。如果所有枚举的模块都是可观察的,那么第二步将计算它们的可读性图。可读性图体现了模块之间的依赖关系,进而控制跨模块边界的访问。

步骤1:递归枚举

递归枚举获取一组模块名称,查找每个模块声明,并对每个模块声明进行递归枚举:

  • 由带有'transitive'修饰符的'requires'指令给出的模块名称,以及

  • 根据主机系统的判断,由不带有'transitive'修饰符的'requires'指令给出的模块名称。

模块声明在一组可观察模块中查找。可观察模块的集合以实现特定的方式确定。可观察模块的集合可能包括具有显式声明(即具有module-info.java源文件或module-info.class文件)和具有隐式声明(即自动模块)的模块。因为自动模块没有显式模块声明,因此它没有自己的'requires'指令,尽管其名称可能由显式模块声明的'requires'指令给出。

作为此算法的初始输入的根模块集合以实现特定的方式确定。根模块集合可能包括自动模块。

如果此算法枚举了至少一个自动模块,则必须枚举每个可观察的自动模块,无论其名称是否由显式模块声明的'requires'指令给出。

如果发生以下任何条件,则解析失败:

  • 任何根模块不可观察。

  • 任何模块的名称由带有'transitive'修饰符的'requires'指令给出,但该模块不可观察。

  • 根据主机系统的判断,任何模块的名称由不带有'transitive'修饰符的'requires'指令给出,但该模块不可观察。

  • 此步骤中的算法两次枚举相同的模块名称。这表示'requires'指令中存在循环,忽略任何'transitive'修饰符。

否则,解析将继续到第2步。

步骤2:计算可读性图

'requires'指令(不考虑'transitive')表示一个模块依赖于另一个模块。'transitive'修饰符的效果是导致其他模块也依赖于另一个模块。如果模块M 'requires transitive N',那么不仅M依赖于N,而且依赖于M的任何模块也依赖于N。这允许对M进行重构,以便将其部分或全部内容移动到新模块N,而不会破坏具有'requires M'指令的模块。

模块依赖关系由可读性图表示。可读性图是一个有向图,其顶点是第1步中枚举的模块,边表示模块对之间的可读性。边的指定如下:

首先,可读性由枚举模块的'requires'指令确定,忽略任何'transitive'修饰符:

  • 对于每个枚举模块A,其'requires' B:A“读取”B。

  • 对于每个自动枚举模块X:X“读取”每个其他枚举模块(它“好像”自动模块对每个其他枚举模块都有'requires'指令)。

其次,可读性增强以考虑'transitive'修饰符:

  • 对于每个“读取”B的枚举模块A:

    • 如果B 'requires transitive' C,则A除了B之外还“读取”C。此增强是递归的:因为A“读取”C,如果C 'requires transitive' D,则A除了C和B之外还“读取”D。

    • 如果B是自动模块,则A“读取”每个其他枚举自动模块。 (“好像”自动模块对每个其他枚举自动模块都有'requires transitive'指令)。

最后,每个模块“读取”自身。

如果可读性图中发生以下任何条件,则解析失败:

  • 一个模块“读取”两个或更多具有相同名称的模块。这包括一个模块“读取”另一个模块,其名称与自身相同的情况。

  • 两个或更多模块向“读取”两者的模块导出具有相同名称的包。这包括一个包含包p的模块M“读取”另一个导出p到M的模块的情况。

  • 模块M声明它'uses p.S'或'provides p.S with ...',但包p既不在模块M中也不被任何M“读取”的模块导出。

否则,解析成功,并且解析的结果是可读性图。

根模块

编译时的根模块集通常是正在编译的模块集。在运行时,根模块集通常是指定给'java'启动器的应用程序模块。当从未命名模块编译代码,或在运行时从类路径加载主应用程序类时,那么默认的根模块集是实现特定的。在JDK中,默认的根模块集包含升级模块路径或系统模块中可观察的每个模块,并且至少导出一个未限定的包。

可观察模块

编译时和运行时的可观察模块集是通过搜索几个不同路径以及搜索环境中内置的编译模块来确定的。搜索顺序如下:

  1. 仅在编译时,编译模块路径。此路径包含源代码形式的模块定义。

  2. 升级模块路径。此路径包含将优先观察的模块的编译定义,而不是存在于(3)和(4)中的任何可升级模块的编译定义。请参阅Java SE平台,了解哪些标准模块可升级。

  3. 系统模块,这些是内置到环境中的编译定义。

  4. 应用程序模块路径。此路径包含库和应用程序模块的编译定义。

'requires'指令带有'static'修饰符

具有'static'修饰符的'requires'指令表示运行时的可选依赖关系。如果模块声明它'requires static M',则解析不会搜索可观察模块以满足依赖关系。但是,如果M在步骤1中递归枚举,则所有枚举的模块和`requires static M`将读取M。

完整性

在编译时,解析可能是部分的,因为可能不需要完整的传递闭包来编译一组模块。最小化,在编译时构建和验证的可读性图包括正在编译的模块、它们的直接依赖关系以及所有隐式声明的依赖关系(requires transitive)。

在运行时,解析是一个增量过程。步骤1中的递归枚举可能相对于先前的解析是相对的,因此根模块或'requires'指令中命名的模块在先前(或父级)解析时未被枚举。因此,解析的结果可能是具有在步骤1中枚举的模块的顶点,但具有表示该模块读取先前(或父级)解析的模块的边的可读性图。

自JDK 9起