Tips
做一个终身学习的人。
在本章节中,主要介绍以下内容:
和
- Module
类读取模块的属性
- ModuleDescriptor
类在运行时更新模块的定义
- Module
模块 API 由可以让你对模块进行编程访问的类和接口组成。 使用 API,可以通过编程方式:
模块 API 很小。 它由大约 15 个类和接口组成,分布在两个包中:
,
- Module
和
- ModuleLayer
类在 java.lang 包中,其余的在 java.lang.module 包中。 下表包含模块 API 中的类的列表,每个类的简要说明。 列表未排序。 首先列出了
- LayerInstantiationException
和
- Module
,因为应用程序开发人员最常使用它们。 所有其他类通常由容器和类库使用。 该列表不包含 Module API 中的异常类。
- ModuleDescriptor
类 | 描述 |
---|---|
Module | 表示运行时模块。 |
ModuleDescriptor | 表示模块描述。 这是不可变类。 |
ModuleDescriptor.Builder | 用于以编程方式构建模块描述的嵌套构建器类。 |
ModuleDescriptor.Exports | 表示模块声明中的 语句的嵌套类。 |
ModuleDescriptor.Opens | 表示模块声明中的 语句的嵌套类。 |
ModuleDescriptor.Provides | 表示模块声明中的 语句的嵌套类。 |
ModuleDescriptor.Requires | 表示模块声明中的 语句的嵌套类。 |
ModuleDescriptor.Version | 表示模块版本字符串的嵌套类。 它包含一个从版本字符串返回其实例的 工厂方法。 |
ModuleDescriptor.Modifier | 枚举类,其常量表示在模块声明中使用的修饰符,例如打开模块的 。 |
ModuleDescriptor.Exports.Modifier | 枚举类,其常量表示在模块声明中用于 语句的修饰符。 |
ModuleDescriptor.Opens.Modifier | 枚举类,其常量表示在模块声明中的 语句上使用的修饰符。 |
ModuleDescriptor.Requires.Modifier | 枚举类,其常量表示在模块声明中的 语句上使用的修饰符。 |
ModuleReference | 模块的内容的引用。 它包含模块的描述及其位置。 |
ResolvedModule | 表示模块图中已解析的模块。 包含模块的名称,其依赖关系和对其内容的引用。 它可以用于遍历模块图中模块的所有传递依赖关系。 |
ModuleFinder | 用于在指定路径或系统模块上查找模块的接口。 找到的模块作为 的实例返回。 它包含工厂方法来获取它的实例。 |
ModuleReader | 用于读取模块内容的接口。 可以从 获取 。 |
Configuration | 表示解析模块的模块图。 |
ModuleLayer | 包含模块图()以及模块图中的模块与类加载器之间的映射。 |
ModuleLayer.Controller | 用于控制 中的模块的嵌套类。 类中的方法返回此类的实例。 |
类的实例代表一个运行时模块。 加载到 JVM 中的每个类型都属于一个模块。JDK 9 在
- Module
类中添加了一个名为
- Class
的方法,该类返回该类所属的模块。 以下代码片段显示了如何获取
- getModule()
的类的模块:
- BasicInfo
- // Get the Class object for of the BasicInfo class
- Class < BasicInfo > cls = BasicInfo.class;
- // Get the module reference
- Module module = cls.getModule();
模块可以是命名或未命名的。
类的
- Module
方法对于命名模块返回 true,对于未命名的模块返回 false。
- isNamed()
每个类加载器都包含一个未命名的模块,其中包含类加载器从类路径加载的所有类型。 如果类加载器从模块路径加载类型,则这些类型属于命名模块。
类的
- Class
方法可能会返回一个命名或未命名的模块。 JDK 9 将一个名为
- getModule()
的方法添加到
- getUnnamedModule()
类中,该类返回类加载器的未命名模块。 在下面的代码片段中,假设
- ClassLoader
类是从类路径加载的,
- BasicInfo
和
- m1
指的是同一个模块:
- m2
- Class < BasicInfo > cls = BasicInfo.class;
- Module m1 = cls.getClassLoader().getUnnamedModule();
- Module m2 = cls.getModule();
类的
- Module
方法返回模块的名称。 对于未命名的模块,返回 null。
- getName()
- // Get the module name
- String moduleName = module.getName();
类中的
- Module
方法返回包含模块中所有包的
- getPackages()
类型。
- Set<String>
方法返回模块的类加载器。
- getClassLoader()
方法返回包含该模块的
- getLayer()
; 如果模块不在图层中,则返回 null。 模块层仅包含命名模块。 所以,这个方法总是为未命名的模块返回 null。
- ModuleLayer
类的实例表示一个模块定义,它是从一个模块声明创建的 —— 通常来自 module-info.class 文件。 模块描述也可以使用
- ModuleDescriptor
类创建。 可以使用命令行选项来扩充模块声明,例如
- ModuleDescriptor.Builder
,
- --add-reads
和
- --add-exports
,并使用
- -add-opens
类中的方法,如
- Module
,
- addReads()
和
- addOpens()
。
- addExports()
表示在模块声明时添加的模块描述,而不是增强的模块描述。
- ModuleDescriptor
类的
- Module
方法返回一个
- getDescriptor()
:
- ModuleDescriptor
- Class < BasicInfo > cls = BasicInfo.class;
- Module module = cls.getModule();
- // Get the module descriptor
- ModuleDescriptor desc = module.getDescriptor();
Tips
是不可变的。 未命名的模块没有模块描述。
- ModuleDescriptor
类的
- Module
方法为未命名的模块返回 null。
- getDescriptor()
还可以使用
类的静态
- ModuleDescriptor
方法从 module-info.class 文件读取模块声明的二进制形式来创建一个
- read()
对象。 以下代码片段从当前目录中读取一个 module-info.class 文件。 为清楚起见排除异常处理:
- ModuleDescriptor
- String moduleInfoPath = "module-info.class";
- ModuleDescriptor desc = ModuleDescriptor.read(new FileInputStream(moduleInfoPath));
类包含以下静态嵌套类,其实例表示模块声明中具有相同名称的语句:
- ModuleDescriptor
请注意,没有
类来表示
- ModuleDescriptor.Uses
语句。 这是因为
- uses
语句可以表示为 String 的服务接口名称。
- uses
语句
- exports
类的实例表示模块声明中的
- ModuleDescriptor.Exports
语句。 类中的以下方法返回导出语句的组件:
- exports
方法对于限定的导出返回 true,对于非限定的导出,返回 false。
- isCualified()
方法返回导出的包的名称。 对于限定的导出,
- source()
方法返回一个不可变的模块名称
- targets()
类型,导出该包,对于非限定的导出,它返回一个空的
- set
。
- set
方法返回一系列
- modifiers()
语句的修饰符,它们是
- exports
枚举的常量,它包含以下两个常量:
- ModuleDescriptor.Exports.Modifier
隐式声明。
- exports
未明确或隐含地声明。
- exports
语句
- opens
类的实例表示模块声明中的一个
- ModuleDescriptor.Opens
语句。 类中的以下方法返回了
- opens
语句的组件:
- opens
方法对于限定的打开返回 true,对于非限定打开,返回 false。
- isCualified()
方法返回打开包的名称。 对于限定的打开,
- source()
方法返回一个不可变的模块名称
- targets()
类型,打开该包,对于非限定打开,它返回一个空
- set
。 该
- set
方法返回一系列的
- modifiers()
语句,它们是嵌套的
- opens
枚举的常量,它包含以下两个常量:
- ModuleDescriptor.Opens.Modifier
隐式声明。
- opens
未明确或隐含地声明。
- opens
语句
- provides
类的实例表示模块声明中特定服务类型的一个或多个
- ModuleDescriptor.Provides
语句。 以下两个
- provides
语句为相同的服务类型 X.Y 指定两个实现类:
- provides
- provides X.Y with A.B;
- provides X.Y with Y.Z;
类的实例将代表这两个语句。 类中的以下方法返回了
- ModuleDescriptor.Provides
语句的组件:
- provides
方法返回提供者类的完全限定类名的列表。 在上一个示例中,返回的列表将包含
- providers()
和
- A.B
。
- Y.Z
方法返回服务类型的全限定名称。 在前面的例子中,它将返回
- service()
.
- X.Y
语句
- requires
类的实例表示模块声明中的
- ModuleDescriptor.Requires
语句。 类中的以下方法返回
- requires
语句的组件:
- requires
假设一个名为 M 的模块有一个
语句被编译。如果 N 的模块版本在编译时可用,则该版本将记录在 M 的模块描述中。
- requires N
方法返回 N 中的
- compiledVersion()
版本。如果 N 的版本没有可用,则该方法返回一个空可选。在
- Optional
语句中指定的模块的模块版本仅在信息方面被记录在模块描述中。模块系统在任何阶段都不使用它。但是,它可以被工具和框架用于诊断目的。例如,一个工具可以验证使用
- requires
语句指定为依赖关系的所有模块必须具有与编译期间记录的相同或更高版本的版本。
- requires
继续前一段中的示例,
方法返回
- rawCompiledVersion()
中的模块 N 的版本。在大多数情况下,
- Optional<String>
和
- compileVersion()
的两个方法将返回相同的模块版本,但是可以以两种不同的格式返回:一个
- rawCompiledVersion()
对象,另一个
- Optional<ModuleDescriptor.Version>
对象。可以拥有一个模块版本无效的模块。这样的模块可以在 Java 模块系统之外创建和编译。可以将具有无效模块版本的模块加载为 Java 模块。在这种情况下,
- Optional<String>
方法返回一个空的
- compileVersion()
,因为模块版本不能被解析为有效的 Java 模块版本,而
- Optional<ModuleDescriptor.Version>
返回一个包含无效模块版本的
- rawCompiledVersion()
。
- Optional<String>
Tips
类的
- ModuleDescriptor.Requires
方法可能返回所需的模块的不可解析版本。
- rawCompiledVersion()
方法返回在
- name()
语句中指定的模块的名称。
- requires
方法返回的是
- modifiers()
语句的一组修饰符,它们是嵌套的
- requires
枚举的常量,它包含以下常量:
- ModuleDescriptor.Requires.Modifier
语句命名的模块。
- requires
类的实例表示一个模块的版本。 它包含一个名为
- ModuleDescriptor.Version
的静态工厂方法,返回其表示指定版本字符串中的版本的实例。 回想一下,你不要在模块的声明中指定模块的版本。 当你将模块代码打包到模块化 JAR(通常使用 jar 工具)时,可以添加模块版本。 javac 编译器还允许在编译模块时指定模块版本。
- parse(String version)
模块版本字符串包含三个组件:
模块版本具有以下形式:
- vNumToken + ('-'preToken + ) ? ('+'buildToken + ) ?
每个组件是一个 token 序列;每个都是非负整数或一个字符串。 token 由标点符号 ",","-" 或 "+" 或从数字序列转换为既不是数字也不是标点符号的字符序列,反之亦然。版本字符串必须以数字开头。 版本号是由一系列由 "." 分隔 token 序列组成。 以第一个 "-" 或 "+" 字符终止。 预发行版本是由一系列由 "." 或 "-" 分隔 token 序列组成。 以第一个 "+" 字符终止。 构建版本是由 ".",",","-" 或 "+" 字符分隔的 token 序列。
类的
- ModuleDescriptor
方法返回
- version()
。
- Optional<ModuleDescriptor.Version>
在包装模块化 JAR 时,还可以在 module-info.class 文件中设置其他模块属性,如如主类名,操作系统名称等。
类包含返回每个这样的属性的方法。
- ModuleDescriptor
类中包含以下令人感兴趣的方法:
- ModuleDescriptor
方法名称很直观,以便了解其目的。 下面这两个方法,需要一些解释:
和
- packages()
。
- provide()
类包含一个名为
- ModuleDescriptor
的方法,
- packages()
类包含一个名为
- Module
的方法。 两者都返回包名的集合。 为什么为了同一目的有两种方法? 事实上,它们有不同的用途。 在
- getPackages()
中,该方法返回在模块声明中定义的包名的集合,无论它们是否被导出。 回想一下,你无法获得一个未命名模块的
- ModuleDescripto
,在这种情况下,可以使用
- ModuleDescriptor
类中的
- Module
方法在未命名模块中获取软件包名称。 另一个区别是
- getPackages()
记录的包名是静态的;
- ModuleDescriptor
记录的包名称是动态的,它记录在调用
- Module
方法时在模块中加载的包。 模块记录在运行时当前加载的所有包。
- getPackages()
方法返回
- provides()
,考虑在模块声明中以下
- Set<ModuleDescriptor.Provides>
语句:
- provides
- provides A.B with X.Y1;
- provides A.B with X.Y2;
- provides P.Q with S.T1;
在这种情况下,该集合包含两个元素 —— 一个服务类型
,一个服务类型
- A.B
。 一个元素的
- P.Q
和
- service()
方法分别返回
- providers()
和
- A.B
,
- X.Y1
的列表。 对于另一个元素的这些方法将返回
- X.Y2
和包含
- P.Q
的的列表。
- S.T1
在本节中,将展示如何在运行时读取有关模块的基本信息的示例。 下面包含名为 com.jdojo.module.api 的模块的模块声明。 它读取三个模块并导出一个包。 两个读取模块 com.jdojo.prime 和 com.jdojo.intro 在前几章中使用过。 需要将这两个模块添加到模块路径中进行编译,并在 com.jdojo.module.api 模块中运行代码。 java.sql 模块是一个 JDK 模块。
- // module-info.java
- module com.jdojo.module.api {
- requires com.jdojo.prime;
- requires com.jdojo.intro;
- requires java.sql;
- exports com.jdojo.module.api;
- }
下面包含一个名为
的类的代码,它使用
- ModuleBasicInfo
和
- Module
类打印三个模块的模块详细信息。
- ModuleDescriptor
- // ModuleBasicInfo.java
- package com.jdojo.module.api;
- import com.jdojo.prime.PrimeChecker;
- import java.lang.module.ModuleDescriptor;
- import java.lang.module.ModuleDescriptor.Exports;
- import java.lang.module.ModuleDescriptor.Provides;
- import java.lang.module.ModuleDescriptor.Requires;
- import java.sql.Driver;
- import java.util.Set;
- public class ModuleBasicInfo {
- public static void main(String[] args) {
- // Get the module of the current class
- Class < ModuleBasicInfo > cls = ModuleBasicInfo.class;
- Module module = cls.getModule();
- // Print module info
- printInfo(module);
- System.out.println("------------------");
- // Print module info
- printInfo(PrimeChecker.class.getModule());
- System.out.println("------------------");
- // Print module info
- printInfo(Driver.class.getModule());
- }
- public static void printInfo(Module m) {
- String moduleName = m.getName();
- boolean isNamed = m.isNamed();
- // Print module type and name
- System.out.printf("Module Name: %s%n", moduleName);
- System.out.printf("Named Module: %b%n", isNamed);
- // Get the module descriptor
- ModuleDescriptor desc = m.getDescriptor();
- // desc will be null for unnamed module
- if (desc == null) {
- Set < String > currentPackages = m.getPackages();
- System.out.printf("Packages: %s%n", currentPackages);
- return;
- }
- Set < Requires > requires = desc.requires();
- Set < Exports > exports = desc.exports();
- Set < String > uses = desc.uses();
- Set < Provides > provides = desc.provides();
- Set < String > packages = desc.packages();
- System.out.printf("Requires: %s%n", requires);
- System.out.printf("Exports: %s%n", exports);
- System.out.printf("Uses: %s%n", uses);
- System.out.printf("Provides: %s%n", provides);
- System.out.printf("Packages: %s%n", packages);
- }
- }
我们以模块模式和传统模式运行 ModuleBasicInfo 类。 以下命令将使用模块模式:
- C: \Java9Revealed > java--module - path com.jdojo.module.api\dist;
- com.jdojo.prime\dist;
- com.jdojo.intro\dist--module com.jdojo.module.api / com.jdojo.module.api.ModuleBasicInfo
输出结果为:
- Module Name: com.jdojo.module.api Named Module: true Requires: [mandated java.base(@9 - ea), com.jdojo.intro, java.sql(@9 - ea), com.jdojo.prime] Exports: [com.jdojo.module.api] Uses: [] Provides: [] Packages: [com.jdojo.module.api]------------------Module Name: com.jdojo.prime Named Module: true Requires: [mandated java.base(@9 - ea)] Exports: [com.jdojo.prime] Uses: [com.jdojo.prime.PrimeChecker] Provides: [] Packages: [com.jdojo.prime]------------------Module Name: java.sql Named Module: true Requires: [transitive java.logging, transitive java.xml, mandated java.base] Exports: [javax.transaction.xa, java.sql, javax.sql] Uses: [java.sql.Driver] Provides: [] Packages: [javax.sql, java.sql, javax.transaction.xa] Now let's run the ModuleBasicInfo class in legacy mode by using the class path as follows: C: \Java9Revealed > java - cp com.jdojo.module.api\dist\com.jdojo.module.api.jar;
- com.jdojo.prime\dist\com.jdojo.prime.jar com.jdojo.module.api.ModuleBasicInfo Module Name: null Named Module: false Packages: [com.jdojo.module.api]------------------Module Name: null Named Module: false Packages: [com.jdojo.module.api, com.jdojo.prime]------------------Module Name: java.sql Named Module: true Requires: [mandated java.base, transitive java.logging, transitive java.xml] Exports: [javax.transaction.xa, javax.sql, java.sql] Uses: [java.sql.Driver] Provides: [] Packages: [java.sql, javax.transaction.xa, javax.sql]
第二次运行,
和
- ModuleBasicInfo
类被加载到应用程序类加载器的未命名模块中,这反映在为两个模块
- PrimeChecker
方法返回 false。 注意
- isNamed()
类的
- Module
方法的动态特性。 当第一次调用它时,它只返回一个包名称 com.jdojo.module.api。 当它第二次被调用时,它返回两个包名称 com.jdojo.module.api 和 com.jdojo.prime。 这是因为未命名模块中的包是从新的包中添加的类型加载到未命名的模块中。 在这两种情况下,java.sql 模块的输出保持不变,因为平台类型始终加载到同一模块中,而与运行 java 启动的模式无关。
- getPackages()
针对模块运行的典型查询包括:
可以使用命令行选项扩充模块描述,并以编程方式使用 Module API。 可以将模块属性的所有查询分为两类:在加载模块后,其结果可能会更改的查询,以及在模块加载后其结果不会更改的查询。
类包含第一类中查询的方法,
- Module
类包含第二类中查询的方法。
- ModuleDescriptor
类为第一类中的查询提供了以下方法:
- Module
方法名称直观足够告诉你他们做了什么。
方法对于命名模块返回 true,对于未命名的模块返回 false。 名称或未命名的模块类型在模块加载完成后不会更改。 此方法在
- isNamed()
中提供,因为无法获取未命名模块的
- Module类
。
- ModuleDescriptor
包含三种方法来告诉你模块的类型以及模块描述符的生成方式。 如果
- ModuleDescriptor
方法是一个打开的模块,则返回 true,否则返回 false。
- isOpen()
方法对于自动命名模块返回 true,否则返回 false。
- isAutomatic()
下面包含名
类的代码,它是 com.jdojo.module.api 模块的成员。 它显示如何查询模块的依赖关系检查,以及软件包是导出还是打开到所有模块或仅对特定模块。
- QueryModule
- // QueryModule.java
- package com.jdojo.module.api;
- import java.sql.Driver;
- public class QueryModule {
- public static void main(String[] args) throws Exception {
- Class < QueryModule > cls = QueryModule.class;
- Module m = cls.getModule();
- // Check if this module can read the java.sql module
- Module javaSqlModule = Driver.class.getModule();
- boolean canReadJavaSql = m.canRead(javaSqlModule);
- // Check if this module exports the com.jdojo.module.api package to all modules
- boolean exportsModuleApiPkg = m.isExported("com.jdojo.module.api");
- // Check if this module exports the com.jdojo.module.api package to java.sql module
- boolean exportsModuleApiPkgToJavaSql = m.isExported("com.jdojo.module.api", javaSqlModule);
- // Check if this module opens the com.jdojo.module.api package to java.sql module
- boolean openModuleApiPkgToJavaSql = m.isOpen("com.jdojo.module.api", javaSqlModule);
- // Print module type and name
- System.out.printf("Named Module: %b%n", m.isNamed());
- System.out.printf("Module Name: %s%n", m.getName());
- System.out.printf("Can read java.sql? %b%n", canReadJavaSql);
- System.out.printf("Exports com.jdojo.module.api? %b%n", exportsModuleApiPkg);
- System.out.printf("Exports com.jdojo.module.api to java.sql? %b%n", exportsModuleApiPkgToJavaSql);
- System.out.printf("Opens com.jdojo.module.api to java.sql? %b%n", openModuleApiPkgToJavaSql);
- }
- }
输出结果为:
- Named Module: true Module Name: com.jdojo.module.api Can read java.sql ? true Exports com.jdojo.module.api ? true Exports com.jdojo.module.api to java.sql ? true Opens com.jdojo.module.api to java.sql ? false
在前几章中,了解了如何使用
,
- --add-exports
和
- --add-opened
命令行选项向模块添加导出和读取。 在本节中,展示如何以编程方式将这些语句添加到模块中。
- --add-reads
类包含以下方法,可以在运行时修改模块声明:
- Module
使用命令行选项和上面的种方法来修改模块的声明有很大的区别。 使用命令行选项,可以修改任何模块的声明。 然而,这些方法是调用者敏感的。 调用这些方法的代码必须在声明被修改的模块中,除了调用
方法。 也就是说,如果无法访问模块的源代码,则无法使用这些方法来修改该模块的声明。 这些方法通常被框架使用,可以适应运行时需要与其他模块交互。
- addOpens()
所有这些方法在处理命名模块时都会抛出 IllegalCallerException,因此调用者不允许调用这些模块。
方法更新模块以将指定的包导出到指定的模块。 如果指定的包已经导出或打开到指定的模块,或者在未命名或打开的模块上调用该方法,则调用该方法将不起作用。 如果指定的包为空或模块中不存在,则抛出 IllegalArgumentException 异常。 调用此方法与向模块声明中添加限定导出具有相同的效果:
- addExports()
- exports < packageName > to < other > ;
方法与
- addOpens()
方法工作方式相同,只是它更新模块以将指定的包打开到指定的模块。 它类似于在模块中添加以下语句:
- addExports()
- opens < packageName > to < other > ;
方法对关于谁可以调用此方法的规则会产生异常。 可以从同一模块的代码调用其他方法。 但是,可以从另一个模块的代码调用一个模块的
- addOpens()
方法。 假设模块 M 使用以下声明将软件包 P 对模块 N 开放:
- addOpens()
- module M {
- opens P to N;
- }
在这种情况下,模块 N 被允许调用模块 M 上的
方法,这允许模块 N 将软件包 P 打开到模块 S。当模块的作者可以将模块的包打开到已知的抽象框架模块时,在模块运行时发现并使用另一个实现模块。动态已知的模块都可能需要对所声明的模块进行深层反射访问。在这种情况下,模块的作者只需要了解抽象框架的模块名称并打开它的包。在运行时,抽象框架的模块可以打开与动态发现的实现模块相同的包。考虑 JPA 作为一个抽象框架,定义了一个 java.persistence 模块,并在运行时发现了其他 JPA 实现,如 Hibernate 和 EclipseLink。在这种情况下,模块的作者只能打开一个包到 java.persistence 模块,该模块可以在运行时打开与 Hibernate 或 EclipseLink 模块相同的软件包。
- addOpens("P", S)
方法将可读性边界从该模块添加到指定的模块。 如果指定的模块本身是因为每个模块都可以读取自身或者由于未命名模块可以读取所有模块而在未命名模块上被调用,则此方法无效。 调用此方法与
- addReads()
语句添加到模块声明中的作用相同:
- requires
- requires < other > ;
方法更新模块以添加服务依赖关系,因此可以使用
- addUses()
类来加载指定服务类型的服务。 在未命名或自动命名模块上调用时不起作用。 其效果与在模块声明中添加以下
- ServiceLoader
语句相同:
- uses
- uses < serviceType > ;
下面包含
类的代码。 它在 com.jdojo.module.api 模块中。 请注意,模块声明不包含
- UpdateModule
语句。 该类包含一个
- uses
方法,它接受一个服务类型作为参数。 它检查模块是否可以加载服务类型。 回想一下,模块必须包含具有指定服务类型的
- findFirstService()
语句,以使用
- uses
类加载该服务类型。 该方法使用
- ServiceLoader
类的
- Module
方法,如果不存在,则为该服务类型添加一个
- addUses()
语句。 最后,该方法加载并返回加载的第一个服务提供者。
- uses
- // UpdateModule.java
- package com.jdojo.module.api;
- import java.util.ServiceLoader;
- public class UpdateModule {
- public static < T > T findFirstService(Class < T > service) {
- /* Before loading the service providers, check if this module can use (or load) the
- service. If not, update the module to use the service.
- */
- Module m = UpdateModule.class.getModule();
- if (!m.canUse(service)) {
- m.addUses(service);
- }
- return ServiceLoader.load(service).findFirst().orElseThrow(() - >new RuntimeException("No service provider found for the service: " + service.getName()));
- }
- }
现在将测试
类的
- UpdateModule
方法。 下面包含名为 com.jdojo.module.api.test 的模块的声明。 它声明对 com.jdojo.prime 模块的依赖,因此它可以使用
- findFirstService()
服务类型接口。 它声明对 com.jdojo.module.api 模块的依赖,因此它可以使用
- PrimeChecker
类加载服务。 需要将这两个模块添加到 NetBeans 中 com.jdojo.module.api.test 模块的模块路径中。
- UpdateModule
- // module-info.java
- module com.jdojo.module.api.test {
- requires com.jdojo.prime;
- requires com.jdojo.module.api;
- }
下面包含 com.jdojo.module.api.test 模块中的
类的代码。
- Main
- // Main.java
- package com.jdojo.module.api.test;
- import com.jdojo.module.api.UpdateModule;
- import com.jdojo.prime.PrimeChecker;
- public class Main {
- public static void main(String[] args) {
- long[] numbers = {
- 3,
- 10
- };
- try {
- // Obtain a service provider for the com.jdojo.prime.PrimeChecker service type
- PrimeChecker pc = UpdateModule.findFirstService(PrimeChecker.class);
- // Check a few numbers for prime
- for (long n: numbers) {
- boolean isPrime = pc.isPrime(n);
- System.out.printf("%d is a prime: %b%n", n, isPrime);
- }
- } catch(RuntimeException e) {
- System.out.println(e.getMessage());
- }
- }
- }
使用以下命令运行 Main 类。 确保将 com.jdojo.intro 模块添加到模块路径,因为 com.jdojo.module.api.test 模块读取 com.jdojo.module.api 模块,该模块读取 com.jdojo.intro 模块。
- C: \Java9Revealed > java--module - path com.jdojo.prime\dist;
- com.jdojo.intro\dist;
- com.jdojo.module.api\dist;
- com.jdojo.module.api.test\dist--module com.jdojo.module.api.test / com.jdojo.module.api.test.Main
输出结果为:
- No service provider found
- for the service: com.jdojo.prime.PrimeChecker
输出显示此程序的正常执行。 这在输出中指示,它没有在模块路径上找到 com.jdojo.prime.PrimeChecker 服务类型的服务提供者。 我们为模块路径上的 com.jdojo.prime.PrimeChecker 服务类型添加一个服务提供者 com.jdojo.prime.generic 模块,并重新运行程序。 如果你向模块路径添加了不同的服务提供者,则可能会得到不同的输出。
- C: \Java9Revealed > java--module - path com.jdojo.prime\dist;
- com.jdojo.intro\dist;
- com.jdojo.module.api\dist;
- com.jdojo.module.api.test\dist;
- com.jdojo.prime.generic\dist--module com.jdojo.module.api.test / com.jdojo.module.api.test.Main
输出结果为:
- 3 is a prime: true 10 is a prime: false
模块可能包含资源,如图像,音频 / 视频剪辑,属性文件和策略文件。 模块中的类文件(.class 文件)也被视为资源。
类包含
- Module
方法来使用资源名称来检索资源:
- getResourceAsStream()
- InputStream getResourceAsStream(String name) throws IOException
可以在模块声明上使用注解。
枚举有一个名为
- java.lang.annotation.ElementType
的新值。 如果在注解声明中使用
- MODULE
作为目标类型,则允许在模块上使用注解。 在 Java 9 中,两个注释
- MODULE
和
- java.lang.Deprecated
已更新为在模块声明中使用。 它们可以使用如下:
- java.lang.SuppressWarnings
- @Deprecated(since = "1.2", forRemoval = true)@SuppressWarnings("unchecked") module com.jdojo.myModule {
- // Module statements go here
- }
当模块被弃用时,使用该模块需要但不在导出或打开语句中,将导致发出警告。 该规则基于以下事实:如果模块 M 不推荐使用,则使用需要 M 的模块的用户获得弃用警告。 诸如导出和打开的其他语句在被弃用的模块中。 不建议使用的模块不会对模块中的类型的使用发出警告。 类似地,如果在模块声明中抑制了警告,则抑制应用于模块声明中的元素,而不适用于该模块中包含的类型。
类实现
- Module
接口,因此可以使用各种与注解相关的方法来读取它们。 要在模块声明中使用的注解类型必须包含
- java.lang.reflect.AnnotatedElement
作为目标。
- ElementType.MODULE
Tips
不能对各个模块语句添加注解。 例如,不能使用 @Deprecated 注解用在语句,表示导出的包将在以后的版本中被删除。 在早期的设计阶段,它是经过考虑和拒绝的,理由是这个功能将需要大量的时间,这是不需要的。 如果需要,可以在将来添加。 因此,将不会在
- exports
类中找到任何与注解相关的方法。
- ModuleDescriptor
现在我们创建一个新的注解类型,并在模块声明中使用它。 如下包含一个名为 com.jdojo.module.api.annotation 的模块的模块声明,该模块包含三个注解。
- // module-info.java
- import com.jdojo.module.api.annotation.Version;@Deprecated(since = "1.2", forRemoval = false)@SuppressWarnings("unchecked")@Version(major = 1, minor = 2) module com.jdojo.module.api.annotation {
- // No module statements
- }
版本注解类型已在同一模块中声明,其源代码如下所示。 新注解类型的保留策略是
。
- RUNTIME
- // Version.java
- package com.jdojo.module.api.annotation;
- import static java.lang.annotation.ElementType.MODULE;
- import static java.lang.annotation.ElementType.PACKAGE;
- import static java.lang.annotation.ElementType.TYPE;
- import java.lang.annotation.Retention;
- import static java.lang.annotation.RetentionPolicy.RUNTIME;
- import java.lang.annotation.Target;@Retention(RUNTIME)@Target({
- PACKAGE,
- MODULE,
- TYPE
- }) public@interface Version {
- int major();
- int minor();
- }
下面包含了一个
类的代码。 它读取 com.jdojo.module.api.annotation 模块上的注解。 输出不包含模块上存在的 @SuppressWarnings 注解,因为此注解使用
- AnnotationTest
的保留策略,这意味着注解不会在运行时保留。
- RetentionPolicy.RUNTIME
- // AnnotationTest.java
- package com.jdojo.module.api.annotation;
- import java.lang.annotation.Annotation;
- public class AnnotationTest {
- public static void main(String[] args) {
- // Get the module reference of the com.jdojo.module.api.annotation module
- Module m = AnnotationTest.class.getModule();
- // Print all annotations
- Annotation[] a = m.getAnnotations();
- for (Annotation ann: a) {
- System.out.println(ann);
- }
- // Read the Deprecated annotation
- Deprecated d = m.getAnnotation(Deprecated.class);
- if (d != null) {
- System.out.printf("Deprecated: since=%s, forRemoval=%b%n", d.since(), d.forRemoval());
- }
- // Read the Version annotation
- Version v = m.getAnnotation(Version.class);
- if (v != null) {
- System.out.printf("Version: major=%d, minor=%d%n", v.major(), v.minor());
- }
- }
- }
输出结果为:
- @java.lang.Deprecated(forRemoval = false, since = "1.2")@com.jdojo.module.api.annotation.Version(major = 1, minor = 2) Deprecated: since = 1.2,
- forRemoval = false Version: major = 1,
- minor = 2
可以使用
类的以下静态
- Class
方法来加载和初始化一个类:
- forName()
在这些方法中,
参数是要加载的类或接口的完全限定名称,例如
- className
和
- java.lang.Thread
。 如果
- com.jdojo.intro.Welcome
参数为 true,则该类将被初始化。
- initialize
方法在加载之后初始化该类,并使用当前的类加载器,该加载器是加载调用此方法的类的类加载器。 表达式
- The forName(String className)
里的实例方法相当于
- Class.forName("P.Q")
,
- Class.forName("P.Q", true, this.getClass().getClassLoader())
下面包含作为 com.jdojo.module.api 模块成员的
类的代码。 该类包含两个版本的
- LoadClass
方法。 该方法加载指定的类,并且在成功加载类之后,它尝试使用无参构造函数来实例化该类。 请注意,com.jdojo.intro 模块不导出包含
- loadClass()
类的 com.jdojo.intro 包。 此示例尝试加载和实例化
- Welcome
类和另外两个不存在的类。
- Welcome
- // LoadingClass.java
- package com.jdojo.module.api;
- import java.lang.reflect.Constructor;
- import java.lang.reflect.InvocationTargetException;
- import java.util.Optional;
- public class LoadingClass {
- public static void main(String[] args) {
- loadClass("com.jdojo.intro.Welcome");
- loadClass("com.jdojo.intro.XYZ");
- String moduleName = "com.jdojo.intro";
- Optional < Module > m = ModuleLayer.boot().findModule(moduleName);
- if (m.isPresent()) {
- Module introModule = m.get();
- loadClass(introModule, "com.jdojo.intro.Welcome");
- loadClass(introModule, "com.jdojo.intro.ABC");
- } else {
- System.out.println("Module not found: " + moduleName + ". Please make sure to add the module to the module path.");
- }
- }
- public static void loadClass(String className) {
- try {
- Class < ?>cls = Class.forName(className);
- System.out.println("Class found: " + cls.getName());
- instantiateClass(cls);
- } catch(ClassNotFoundException e) {
- System.out.println("Class not found: " + className);
- }
- }
- public static void loadClass(Module m, String className) {
- Class < ?>cls = Class.forName(m, className);
- if (cls == null) {
- System.out.println("Class not found: " + className);
- } else {
- System.out.println("Class found: " + cls.getName());
- instantiateClass(cls);
- }
- }
- public static void instantiateClass(Class < ?>cls) {
- try {
- // Get the no-arg constructor
- Constructor < ?>c = cls.getConstructor();
- Object o = c.newInstance();
- System.out.println("Instantiated class: " + cls.getName());
- } catch(InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
- System.out.println(e.getMessage());
- } catch(NoSuchMethodException e) {
- System.out.println("No no-args constructor for class: " + cls.getName());
- }
- }
- }
尝试运行
类,只需将三个必需的模块添加到模块路径中:
- LoadClass
- C: \Java9Revealed > java--module - path com.jdojo.module.api\dist;
- com.jdojo.prime\dist;
- com.jdojo.intro\dist--module com.jdojo.module.api / com.jdojo.module.api.LoadingClass
输出结果为:
- Class found: com.jdojo.intro.Welcome class com.jdojo.module.api.LoadingClass( in module com.jdojo.module.api) cannot access class com.jdojo.intro.Welcome( in module com.jdojo.intro) because module com.jdojo.intro does not export com.jdojo.intro to module com.jdojo.module.api Class not found: com.jdojo.intro.XYZ Class found: com.jdojo.intro.Welcome class com.jdojo.module.api.LoadingClass( in module com.jdojo.module.api) cannot access class com.jdojo.intro.Welcome( in module com.jdojo.intro) because module com.jdojo.intro does not export com.jdojo.intro to module com.jdojo.module.api Class not found: com.jdojo.intro.ABC
输出显示我们可以加载
类。 但是,我们无法将其实例化,因为它不会导出到 com.jdojo.intro 模块中。 以下命令使用
- com.jdojo.intro.Welcome
选项将 com.jdojo.intro 模块中的 com.jdojo.intro 包导出到 com.jdojo.module.api 模块。 输出显示我们可以加载并实例化
- --add-exports
类。
- Welcome
- c: \Java9Revealed > java--module - path com.jdojo.module.api\dist;
- com.jdojo.prime\dist;
- com.jdojo.intro\dist--add - exports com.jdojo.intro / com.jdojo.intro = com.jdojo.module.api--module com.jdojo.module.api / com.jdojo.module.api.LoadingClass
输出结果为:
- Class found: com.jdojo.intro.Welcome Instantiated class: com.jdojo.intro.Welcome Class not found: com.jdojo.intro.XYZ Class found: com.jdojo.intro.Welcome Instantiated class: com.jdojo.intro.Welcome Class not found: com.jdojo.intro.ABC
使用模块层是一个高级主题。 典型的 Java 开发人员不需要直接使用模块层。 现有的应用程序不会使用模块层。 如果将应用程序迁移到 JDK 9 或使用 JDK 9 开发新的应用程序,无论是否需要,都至少使用一个由 JVM 在启动时创建的模块层。 通常,使用插件或容器架构的应用程序将使用模块层。
层是一组解析的模块(一个模块图),具有将每个模块映射到负责加载该模块中所有类型的类加载器的功能。 解析的模块集合称为配置。 可以可视化模块,类加载器,配置和层之间的关系,如下所示:
模块排列成层。 层次分层排列。 层除了空层以外还有至少一个父层,顾名思义,该层不包含模块,主要存在作为引导层的父层。 引导层由启动时由 JVM 创建,通过针对一组可观察模块解析应用程序的初始模块(根模块)。 使用类加载器的加载类型在 JDK 9 中没有变化。加载器通常使用父类——第一委托机制的模式,其中将加载类型的请求委托给父进程,而父请求委托给其父进程,直到引导类加载器。 如果父节点中没有一个加载类型,那么最初收到请求的类加载器就会加载它。 下图给出了模块,类装载器和层的布置方式的示例。
在图中,从 X 到 Y 的箭头意味着 X 是 Y 的父类,其中 X 和 Y 可以是类加载器或层。 层是堆放的 —— 空层和引导层是最低的两层。 我们进一步的讨论中忽略引用空层,并将启动层作为堆栈层中的最低层。 引导层是名为 Layer1 和 Layer2 的两个自定义层的父层。
堆叠中给定层中的模块可以在其下方的层中读取模块。 也就是说,Layer1 和 Layer2 都可以读取引导层中的模块。 但是,Layer1 无法读取 Layer2 中的模块,因为它们是兄弟层。 引导层也不能读取 Layer1 和 Layer2 中的模块,因为引导层是它们的父层。 如图上所示,两个用户定义的层中的类加载器都将应用程序类加载器作为其父类,这通常是这种情况。 使应用程序类加载器成为自定义类加载器的父级,确保后者能够读取引导层中模块中的所有类型。 当模块在一层读取下一层模块时,模块的可读性属性受到重视。
允许将模块布置成层次可用于两个用例(覆盖机制和扩展机制),这些机制和扩展机制通常在高级 Java 应用程序(例如作为托管应用程序容器的 Java EE 应用程序 / Web 服务器)中遇到。 在覆盖机制中,托管应用程序需要覆盖容器提供的功能,例如使用同一模块的不同版本。 在扩展机制中,托管应用程序需要补充容器提供的功能,例如提供其他服务提供者。 在上图中,com.jdojo.test 模块位于引导层以及 Layer1 中。 这是覆盖模块的情况。 Layer1 中的模块版本将被 Layer1 使用,而 Layer2 将使用引导层中的该模块的版本。
通常需要容器允许托管应用程序提供自己的一组可以覆盖容器中嵌入的模块。 这可以通过将托管应用程序的模块加载到容器层顶部的图层中实现。 加载到特定应用层的模块将覆盖服务器级别层中的模块。 这样,可以在同一个 JVM 中使用同一模块的多个版本。
托管应用程序可能希望使用与容器提供的不同的服务提供者。 通过将应用程序特定的服务提供程序模块添加到容器层顶部的图层可以实现。 可以使用
类的
- ServiceLoader
方法来加载服务提供者。 指定的层将是托管的应用程序特定层。 此方法从指定的层及其父层加载服务提供者。
- load(ModuleLayer layer, Class<S> service)
Tips
层是不可变的。 创建图层后,无法向其中添加模块或从中删除模块。 如果需要添加模块或替换其他版本的模块,则必须拆除图层并重新创建。
创建图层是一个多步骤的过程。 需要:
创建图层后,可以使用它来加载类型。 将在下一节详细介绍这些步骤。 最后,展示多个版本的模块如何使用图层。
模块查找器是
接口的一个实例。 它用于在模块解析和服务绑定期间查找
- ModuleFinder
。 该接口包含两种工厂方法来创建模块查找器:
- ModuleReferences
方法通过搜索指定的路径序列来定位模块,这些路径可以是目录或打包模块的路径。 该方法首先发现模块名称按顺序搜索指定的路径。 以下代码片段显示了如何创建一个在 C:\Java9Revealed\lib 和 C:\Java9Revealed\customLib 目录中搜索模块的模块查找器:
- of()
- // Create the module paths
- Path mp1 = Paths.get("C:\\Java9Revealed\\lib");
- Path mp2 = Paths.get("C:\\Java9Revealed\\customLib");
- // Create a module finder using two module paths
- ModuleFinder finder = ModuleFinder.of(mp1, mp2);
有时候,需要一个
引用,例如传递给一个方法,但该模块查找器不需要查找任何模块。 可以使用
- ModuleFinder
方法,而不需要任何路径作为参数创建,例如模块查找器。
- ModuleFinder.of()
方法返回一个模块查找器,它可以查找链接到运行时的系统模块。 该方法始终找到 java.base 模块。 请注意,可以将自定义的一组模块链接到运行时映像,这意味着使用此方法定位的模块取决于运行时映像。 自定义运行时映像包含 JDK 模块以及应用程序模块。 该方法将找到两种类型的模块。
- ofSystem()
还可以使用
方法从零个更多的模块查找器的序列中组成一个模块查找器:
- compose()
- static ModuleFinder compose(ModuleFinder...finders)
该模块查找器将按照指定的顺序使用每个模块查找器。 第二个模块查找器将找到第一个模块查找器未找到的所有模块,第三个模块查找器将找到第一个和第二个模块查找器未找到的所有模块,依此类推。
接口包含以下方法来查找模块:
- ModuleFinder
方法查找具有指定名称的模块。
- find()
方法查找发现者可以找到的所有模块。
- findAll()
以下包含
类的代码,显示如何使用
- FindingModule
。 代码在 Windows 上使用路径,如 C:\Java9Revealed\lib,此目录存储模块。 你可能需要在运行该类之前更改模块路径。 该类是 com.jdojo.module.api 模块的成员。 可能会得到不同的输出。
- ModuleFinder
- // FindingModule.java
- package com.jdojo.module.api;
- import java.lang.module.ModuleDescriptor;
- import java.lang.module.ModuleFinder;
- import java.lang.module.ModuleReference;
- import java.net.URI;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.util.Optional;
- import java.util.Set;
- public class FindingModule {
- public static void main(String[] args) {
- // Create module paths
- Path mp1 = Paths.get("C:\\Java9Revealed\\lib");
- Path mp2 = Paths.get("C:\\Java9Revealed\\customLib");
- // Create a module finder
- ModuleFinder finder = ModuleFinder.of(mp1, mp2);
- // Find all modules that this finder can locate
- Set < ModuleReference > moduleRefs = finder.findAll();
- // Print the details of the modules found
- moduleRefs.forEach(FindingModule: :printInfo);
- }
- public static void printInfo(ModuleReference mr) {
- ModuleDescriptor md = mr.descriptor();
- Optional < URI > location = mr.location();
- URI uri = null;
- if (location.isPresent()) {
- uri = location.get();
- }
- System.out.printf("Module: %s, Location: %s%n", md.name(), uri);
- }
- }
输出结果为:
- Module: com.jdojo.prime.probable,
- Location: file: ///C:/Java9Revealed/lib/com.jdojo.prime.probable.jar
- Module: com.jdojo.person,
- Location: file: ///C:/Java9Revealed/lib/com.jdojo.person.jar
- Module: com.jdojo.address,
- Location: file: ///C:/Java9Revealed/lib/com.jdojo.address.jar
- ...
在上一节中,学习了如何使用
查找模块引用,它是
- ModuleFinder
类的实例。
- ModuleReference
封装了
- ModuleReference
和模块的位置。 可以使用
- ModuleDescriptor
类的
- ModuleReference
方法来获取
- open()
接口的实例。
- ModuleReader
用于列出,查找和读取模块的内容。 以下代码片段显示了如何获取 java.base 模块的
- ModuleReader
:
- ModuleReader
- // Create a system module finder
- ModuleFinder finder = ModuleFinder.ofSystem();
- // The java.base module is guaranteed to exist
- Optional < ModuleReference > omr = finder.find("java.base");
- ModuleReference moduleRef = omr.get();
- // Get a module reader
- ModuleReader reader = moduleRef.open();
类的
- ModuleReference
方法抛出一个 IOException 异常。 在这段代码中省略了异常处理,以保持代码简单。
- open()
中的以下方法用于处理模块的内容。 方法名称足够直观地告诉你他们做了什么。
- ModuleReader
传递给这些方法的资源名称是 "/" 分隔的路径字符串。 例如,java.base 模块中 java.lang.Object 类的资源名称为 java/lang/Object.class。
一旦完成了使用
,需要使用
- ModuleReader
方法关闭它。 如果尝试使用已经关闭的
- close()
读取模块的内容,则会抛出 IOException 异常。
- ModuleReader
方法返回一个
- read()
。 需要调用
- Optional<ByteBuffer>
方法来释放字节缓冲区,以避免资源泄漏。
- release(ByteBuffer bb)
下列包含一个程序,显示如何读取模块的内容。 它读取
中
- ByteBuffer
对象的内容,并以字节为单位打印其大小。 它还在 java.base 模块中打印五个资源的名称。 你可能会得到不同的输出。
- Object
- // ReadingModuleContents.java
- package com.jdojo.module.api;
- import java.io.IOException;
- import java.lang.module.ModuleFinder;
- import java.lang.module.ModuleReader;
- import java.lang.module.ModuleReference;
- import java.nio.ByteBuffer;
- import java.util.Optional;
- public class ReadingModuleContents {
- public static void main(String[] args) {
- // Create a system module finder
- ModuleFinder finder = ModuleFinder.ofSystem();
- // The java.base module is guaranteed to exist
- Optional < ModuleReference > omr = finder.find("java.base");
- ModuleReference moduleRef = omr.get();
- // Get a module reader and use it
- try (ModuleReader reader = moduleRef.open()) {
- // Read the Object class and print its size
- Optional < ByteBuffer > bb = reader.read("java/lang/Object.class");
- bb.ifPresent(buffer - >{
- System.out.println("Object.class Size: " + buffer.limit());
- // Release the byte buffer
- reader.release(buffer);
- });
- System.out.println("\nFive resources in the java.base module:");
- reader.list().limit(5).forEach(System.out: :println);
- } catch(IOException e) {
- e.printStackTrace();
- }
- }
- }
输出结果为:
- Object.class Size: 1859 Five resources in the java.base module: module - info.class sun / util / BuddhistCalendar.class sun / util / PreHashedMap$1$1.class sun / util / PreHashedMap$1.class sun / util / PreHashedMap$2$1$1.class
配置表示一组已解析的模块。 解析的模块是一个使用
语句指定的依赖关系的模块。 模块解决过程使用两组模块:一组根模块和一组可观察模块。 根模块集合中的每个模块都用作初始模块,其
- requires
语句针对可观察模块集合进行解析。 根模块可能需要另一个模块,这可能需要另一个模块,等等。 解决过程计算所有根模块的依赖链。 所得到的模块图被称为依赖图。
- requires
依赖图只考虑了
语句。 如果一个模块使用了
- requires
语句,则依赖于此模块的模块将隐含地依赖于在必需传递语句中指定的模块。 依赖关系图增加了
- requires transitive
语句模块的额外可读性,从而产生一个称为可读性图的模块图。
- requires transitive
模块中的
和
- uses
语句也构成依赖关系。 如果模块 M 使用服务类型 S,并且另一个模块 N 提供 T 的实现 S,则模块 M 依赖于使用服务类型 S 的模块 N。可读性图用针对这样的服务使用依赖性计算的模块进行扩充。
- provides
当创建引导层的配置时,它通过解析依赖关系(
语句),隐含的可读性(
- requires
)和服务使用依赖性(
- requires transitive
和
- uses
语句)来包含模块。 为用户定义的层创建配置时,可以选择包含或排除服务使用依赖关系。
- provides
类的实例表示一个配置。 一个配置至少有一个父类,除了一个空配置。
- Configuration
类的实例表示配置中已解析的模块。 它的
- ResolvedModule
方法返回一个已解析的模块读取的
- reads()
。
- Set<ResolvedModule>
方法返回解析的模块是其成员的配置。
- configuration()
方法返回一个
- reference()
,可以使用它来获取
- ModuleReference
来读取模块的内容。
- ModuleReader
类中的以下方法创建一个
- Configuration
对象:
- Configuration
- static Configuration empty() Configuration resolve(ModuleFinder before, ModuleFinder after, Collection < String > roots) Configuration resolveAndBind(ModuleFinder before, ModuleFinder after, Collection < String > roots) static Configuration resolve(ModuleFinder before, List < Configuration > parents, ModuleFinder after, Collection < String > roots) static Configuration resolveAndBind(ModuleFinder before, List < Configuration > parents, ModuleFinder after, Collection < String > roots)
方法返回一个空配置。 这主要用于配置引导层的父配置。
- empty()
有两个版本的
和
- resolve()
方法:一个是实例方法,另一个为静态方法。 他们之间只有一个区别。 实例方法使用当前配置作为父配置来创建新配置,而静态方法可让你传递新配置的父配置列表。
- resolveAndBind()
方法的工作方式与
- resolveAndBind()
方法相同,只不过它也解决了服务使用依赖关系。 以下代码片段显示了如何使用引导层配置作为其父配置来创建配置:
- resolve()
- // Define the module finders
- String modulePath = "C:\\Java9Revealed\\customLib";
- Path path = Paths.get(modulePath);
- ModuleFinder beforFinder = ModuleFinder.of(path);
- // Our after module finder is empty
- ModuleFinder afterFinder = ModuleFinder.of();
- // Set up the root modules
- Set < String > rootModules = Set.of("com.jdojo.layer");
- // Create a configuration using the boot layer's configuration as its parent configuration
- Configuration parentConfig = ModuleLayer.boot().configuration();
- Configuration config = parentConfig.resolve(beforFinder, afterFinder, rootModules);
类中的以下方法用于检索配置中已解析模块的详细信息:
- Configuration
- Optional < ResolvedModule > findModule(String name) Set < ResolvedModule > modules() List < Configuration > parents()
这些方法的名称和签名是直观的,足以理解它们的使用。 在下一节中,介绍如何使用配置来创建模块层。
模块层是将每个模块映射到类加载器的配置和功能。 要创建一个图层,必须先创建一个配置,并有一个或多个类加载器将模块映射到它们。 模块的类加载器负责加载该模块中的所有类型。 可以将配置中的所有模块映射到一个类加载器;也可以将每个模块映射到不同的类加载器;或者可以有自定义映射策略。 通常,类加载器使用委派策略来将类加载请求委托给其父类加载器。 当为层中的模块定义类加载器时,也可以使用此策略。
java.lang 包中的
类的实例代表一个模块层。 该类包含两个方法,
- ModuleLayer
和
- empty()
,它们分别返回一个空配置的空层和引导层。 类中的以下方法用于创建自定义图层:
- boot()
- ModuleLayer defineModules(Configuration cf, Function < String, ClassLoader > clf) static ModuleLayer.Controller defineModules(Configuration cf, List < ModuleLayer > parentLayers, Function < String, ClassLoader > clf) ModuleLayer defineModulesWithManyLoaders(Configuration cf, ClassLoader parentClassLoader) static ModuleLayer.Controller defineModulesWithManyLoaders(Configuration cf, List < ModuleLayer > parentLayers, ClassLoader parentLoader) ModuleLayer defineModulesWithOneLoader(Configuration cf, ClassLoader parentClassLoader) static ModuleLayer.Controller defineModulesWithOneLoader(Configuration cf, List < ModuleLayer > parentLayers, ClassLoader parentLoader)
方法有两个变体:一个集合包含实例方法,另一个集合包含静态方法。 实例方法使用它们被称为父层的层,而静态方法可以指定新层的父层列表。 静态方法返回一个
- defineModulesXxx()
对象,可以使用它来处理新层中的模块。
- ModuleLayer.Controller
是 java.lang 包中的一个嵌套类,具有以下方法:
- ModuleLayer.Controller
- ModuleLayer.Controller addOpens(Module source, String packageName, Module target) ModuleLayer.Controller addReads(Module source, Module target) ModuleLayer layer()
和
- addOpens()
方法可以让这个层中的一个模块中的一个包对另一个模块开放,并将这个层中的模块的读取边界加到另一个模块。
- addReads()
方法返回该控制器正在管理的
- layer()
。
- ModuleLayer
方法将配置作为其第一个参数。 第二个参数是映射函数,它在配置中获取模块名,并为该模块返回类加载器。 方法调用可能会失败,如果:
- defineModules(Configuration cf, Function<String,ClassLoader> clf)
方法使用指定的配置创建一个模块层。 配置中的每个模块都映射到由此方法创建的不同类加载器。 指定的父类加载器(第二个参数)被设置为通过此方法创建的类加载器的父级。 通常,使用应用程序类加载器作为由此方法创建的所有类加载器的父类加载器。 可以使用 null 作为第二个参数来使用引导类加载器作为由此方法创建的所有类加载器的父级。 该方法将为配置中的每个模块创建一个新的类加载器。
- defineModulesWithManyLoaders(Configuration cf, ClassLoader parentClassLoader)
方法使用指定的配置创建一个模块层。 它使用指定的父类加载器作为其父类创建一个类加载器。 它将配置中的所有模块映射到该类加载器。 可以使用 null 作为第二个参数来使用引导类加载器作为由此方法创建的所有类加载器的父级。
- defineModulesWithOneLoader(Configuration cf, ClassLoader parentClassLoader)
以下代码段创建一个层,引导层作为其父层。 层中的所有模块将由一个类加载器加载,父类是系统类加载器。
- Configuration config =
- /* create a configuration... */
- ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
- ModuleLayer parentLayer = ModuleLayer.boot();
- ModuleLayer layer = parentLayer.defineModulesWithOneLoader(config, sysClassLoader);
创建图层后,需要从该图层中的模块加载类。 模块中的所有类型都由映射到该模块的类加载器加载。 请注意,可能在多个层中定义了相同的模块,但这些模块将被映射到不同的类加载器。
类包含一个
- ModuleLayer
方法,它接受模块名称作为参数,并返回该模块的类加载器。 如果模块未在层中定义,则会检查父层。 如果模块不存在于此层或其祖先层中,则会抛出 IllegalArgumentException 异常。 一旦获得了模块的类加载器,可以调用它的 `loadClass(String className) 方法从该模块加载一个类。 以下代码片段(不包括异常处理逻辑)显示了如何在图层中加载类:
- findLoader(String moduleName)
- ModuleLayer layer =
- /* create a layer... */
- // Load a class using the layer
- String moduleName = "com.jdojo.layer";
- String className = "com.jdojo.layer.LayerInfo";
- Class < ?>cls = layer.findLoader(moduleName).loadClass(className);
获得
对象后,可以使用它来实例化其对象并调用该对象的方法。 以下代码段创建一个加载类的对象,并在该对象上调用
- Class
的方法:
- printInfo
- // A method name that prints the details of an object
- String methodName = "printInfo";
- // Instantiate the class using its no-args constructor
- Object obj = cls.getConstructor().newInstance();
- // Find the method
- Method method = cls.getMethod(methodName);
- // Call the method that will print the details
- method.invoke(obj);
类中的以下方法可用于获取有关模块层本身或模块层中包含的模块的信息:
- ModuleLayer
- Optional < Module > findModule(String moduleName) Set < Module > modules() List < ModuleLayer > parents()
方法在层或其父层中查找具有指定名称的模块。
- findModule()
方法返回层中的一组模块,如果该层不包含任何模块,那么它可能是一个空集合。
- modules()
方法返回此图层的父层列表,如果是空层则为空。
- parent()
接下来,介绍如何创建自定义层的完整示例,以及如何在同一应用程序中将两个版本的同一模块加载到两个层中。
模块名称是 com.jdojo.layer,它由一个名为 com.jdojo.layer 的包,它只包含一个名为
的类。 有两个版本的相同模块,所以一切都将重复。 在源代码中创建了两个名为 com.jdojo.layer.v1 和 com.jdojo.layer.v2 的 NetBeans 项目。
- LayerInfo
下面包含 com.jdojo.layer 模块的模块定义的版本 1.0
- // module-info.com version 1.0
- module com.jdojo.layer {
- exports com.jdojo.layer;
- }
接下来是
类的声明。
- LayerInfo
- // LayerInfo.java
- package com.jdojo.layer;
- public class LayerInfo {
- private final static String VERSION = "1.0";
- static {
- System.out.println("Loading LayerInfo version " + VERSION);
- }
- public void printInfo() {
- Class cls = this.getClass();
- ClassLoader loader = cls.getClassLoader();
- Module module = cls.getModule();
- String moduleName = module.getName();
- ModuleLayer layer = module.getLayer();
- System.out.println("Class Version: " + VERSION);
- System.out.println("Class Name: " + cls.getName());
- System.out.println("Class Loader: " + loader);
- System.out.println("Module Name: " + moduleName);
- System.out.println("Layer Name: " + layer);
- }
- }
类非常简单。 它将其版本信息保持在
- LayerInfo
静态变量中。 它在包含版本信息的静态初始化程序中打印一条消息。 此消息将帮助你了解哪个版本的
- VERSION
类正在加载。
- LayerInfo
方法打印类的详细信息:版本,类名,类加载器,模块名称和模块层。
- printInfo()
下面分别包含 com.jdojo.layer 模块的模块定义的 2.0 版本和
类的类声明。 只有一件事情从这个模块的版本 1.0 改为版本 2.0,静态变量 VERSION 的值从 1.0 变为 2.0。
- LayerInfo
- // module-info.com version 2.0
- module com.jdojo.layer {
- exports com.jdojo.layer;
- }
- // LayerInfo.java
- package com.jdojo.layer;
- public class LayerInfo {
- private final static String VERSION = "2.0";
- static {
- System.out.println("Loading LayerInfo version " + VERSION);
- }
- public void printInfo() {
- Class cls = this.getClass();
- ClassLoader loader = cls.getClassLoader();
- Module module = cls.getModule();
- String moduleName = module.getName();
- ModuleLayer layer = module.getLayer();
- System.out.println("Class Version: " + VERSION);
- System.out.println("Class Name: " + cls.getName());
- System.out.println("Class Loader: " + loader);
- System.out.println("Module Name: " + moduleName);
- System.out.println("Layer Name: " + layer);
- }
- }
可以测试模块层,并将 com.jdojo.layer 模块的两个版本都加载到同一个 JVM 中的两个不同的层中。 为此模块的版本 2.0 创建一个模块化 JAR,将其命名为 com.jdojo.layer.v2.jar 或给任何其他所需的名称,并将模块化 JAR 放入 C:\Java9Revealed\customLib 目录中。
测试模块层的程序在 com.jdojo.layer.test 模块中,其声明下所示。 该模块声明对 com.jdojo.layer 模块的版本 1.0 的依赖。 如何确保 com.jdojo.layer 模块的 1.0 版与 com.jdojo.layer.test 模块一起使用? 所有需要做的是在运行 com.jdojo.layer.test 模块时将 com.jdojo.layer 模块的 1.0 版代码放在模块路径上。 要在 NetBeans 中实现此目的,请将 com.jdojo.layer.v1 项目添加到 com.jdojo.layer.test 模块的模块路径中。
- // module-info.java
- module com.jdojo.layer.test {
- // This module reads version 1.0 of the com.jdojo.layer module
- requires com.jdojo.layer;
- }
下面包含了
类的代码,它包含了创建自定义层并将模块加载到其中的逻辑。 此类中使用的逻辑的详细说明遵循此类的输出。
- LayerTest
- // LayerTest.java
- package com.jdojo.layer.test;
- import java.lang.module.Configuration;
- import java.lang.module.ModuleFinder;
- import java.lang.reflect.Method;
- import java.nio.file.Path;
- import java.nio.file.Paths;
- import java.util.Set;
- public class LayerTest {
- public static void main(String[] args) {
- /* Location for the custom module. You will need to change the
- path to point to a directory on your PC that contains the
- modular JAR for the com.jdojo.layer (version 2.0) module.
- */
- final String CUSTOM_MODULE_LOCATION = "C:\\Java9Revealed\\customLib";
- // Define the set of root modules to be resolved in the custom layer
- Set < String > rootModules = Set.of("com.jdojo.layer");
- // Create a custom layer
- ModuleLayer customLayer = createLayer(CUSTOM_MODULE_LOCATION, rootModules);
- // Test the class in the boot layer
- ModuleLayer bootLayer = ModuleLayer.boot();
- testLayer(bootLayer);
- System.out.println();
- // Test the class in the custom layer
- testLayer(customLayer);
- }
- public static ModuleLayer createLayer(String modulePath, Set < String > rootModules) {
- Path path = Paths.get(modulePath);
- // Define the module finders to be used in creating a
- // configuration for the custom layer
- ModuleFinder beforFinder = ModuleFinder.of(path);
- ModuleFinder afterFinder = ModuleFinder.of();
- // Create a configuration for the custom layer
- Configuration parentConfig = ModuleLayer.boot().configuration();
- Configuration config = parentConfig.resolve(beforFinder, afterFinder, rootModules);
- /* Create a custom layer with one class loader. The parent for
- the class loader is the system class loader. The boot layer is
- the parent layer of this custom layer.
- */
- ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
- ModuleLayer parentLayer = ModuleLayer.boot();
- ModuleLayer layer = parentLayer.defineModulesWithOneLoader(config, sysClassLoader);
- // Check if we loaded the module in this layer
- if (layer.modules().isEmpty()) {
- System.out.println("\nCould not find the module " + rootModules + " at " + modulePath + ". " + "Please make sure that the com.jdojo.layer.v2.jar exists " + "at this location." + "\n");
- }
- return layer;
- }
- public static void testLayer(ModuleLayer layer) {
- final String moduleName = "com.jdojo.layer";
- final String className = "com.jdojo.layer.LayerInfo";
- final String methodName = "printInfo";
- try {
- // Load the class
- Class < ?>cls = layer.findLoader(moduleName).loadClass(className);
- // Instantiate the class using its no-args constructor
- Object obj = cls.getConstructor().newInstance();
- // Find the method
- Method method = cls.getMethod(methodName);
- // Call the method that will print the details
- method.invoke(obj);
- } catch(Exception e) {
- e.printStackTrace();
- }
- }
- }
方法声明
- main()
的变量,它 b 保存 com.jdojo.layer 模块 2.0 版本的位置。 必须将路径更改为指向计算机上包含 com.jdojo.layer 模块版本 2.0 的编译模块代码的目录。
- CUSTOM_MODULE_LOCATION
- final String CUSTOM_MODULE_LOCATION = "C:\\Java9Revealed\\customLib";
下面代码保存 com.jojo.layer 作为自定义图层配置的唯一根模块:
- Set < String > rootModules = Set.of("com.jdojo.layer");
调用
方法来创建自定义模块层。 该方法使用逻辑在
- createLayer()
创建 com.jdojo.layer 模块版本 2.0 的自定义层:
- CUSTOM_MODULE_LOCATION
- ModuleLayer customLayer = createLayer(CUSTOM_MODULE_LOCATION, rootModules);
方法获取引导层的引用:
- main()
- ModuleLayer bootLayer = ModuleLayer.boot();
现在,
方法被调用一次用于引导层,一次用于自定义层。 该方法在模块层中找到 com.jdojo.layer 模块的类加载器,并加载
- testLayer()
类。
- com.jdojo.layer.LayerInfo
- final String moduleName = "com.jdojo.layer";
- final String className = "com.jdojo.layer.LayerInfo";
- final String methodName = "printInfo";
- Class < ?>cls = layer.findLoader(moduleName).loadClass(className);
使用无参构造方法创建
对象。
- LayerInfo
- Object obj = cls.getConstructor().newInstance();
最后,获取了
类的
- LayerInfo
方法的引用,并调用了
- printInfo()
方法,该方法打印了
- printInfo()
类的详细信息:
- LayerInfo
- Method method = cls.getMethod(methodName);
- method.invoke(obj);
可以在 NetBeans 中运行
类,也可以使用以下命令。 可能会得到不同的输出。 层名称是该层中所有模块的列表,由
- LayerTest
类的
- ModuleLayer
方法返回。
- toString()
- C: \Java9Revealed > java--module - path com.jdojo.layer.v1\dist;
- com.jdojo.layer.test\dist--module com.jdojo.layer.test / com.jdojo.layer.test.LayerTest
输出结果为:
- Loading LayerInfo version 1.0 Class Version: 1.0 Class Name: com.jdojo.layer.LayerInfo Class Loader: jdk.internal.loader.ClassLoaders$AppClassLoader@6e3c1e69 Module Name: com.jdojo.layer Layer Name: java.security.jgss,
- jdk.unsupported,
- jdk.jlink,
- jdk.security.jgss,
- jdk.javadoc,
- jdk.crypto.cryptoki,
- java.naming,
- jdk.jartool,
- java.xml.crypto,
- jdk.deploy,
- java.logging,
- jdk.snmp,
- jdk.zipfs,
- jdk.crypto.mscapi,
- jdk.naming.dns,
- java.smartcardio,
- java.base,
- jdk.crypto.ec,
- jdk.dynalink,
- jdk.compiler,
- java.compiler,
- jdk.jdeps,
- java.rmi,
- java.xml,
- com.jdojo.layer.test,
- jdk.management,
- java.datatransfer,
- jdk.scripting.nashorn,
- java.desktop,
- java.management,
- jdk.naming.rmi,
- java.scripting,
- jdk.localedata,
- jdk.accessibility,
- jdk.charsets,
- com.jdojo.layer,
- java.security.sasl,
- jdk.security.auth,
- jdk.internal.opt,
- java.prefs Loading LayerInfo version 2.0 Class Version: 2.0 Class Name: com.jdojo.layer.LayerInfo Class Loader: jdk.internal.loader.Loader@4cb2c100 Module Name: com.jdojo.layer Layer Name: com.jdojo.layer
模块 API 由类和接口组成,可以编程的方式访问模块。 使用 API,可以以编程方式读取 / 修改 / 构建模块描述,加载模块,读取模块的内容,创建模块层等。模块 API 很小,包含大约 15 个类和接口,分布在两个包之间:java.lang 和 java.lang.module。
,
- Module
和
- ModuleLayer
类在 java.lang 包中,其余的在 java.lang.module 包中。
- LayerInstantiationException
类的实例代表运行时模块。 加载到 JVM 中的每个类型都属于一个模块。 JDK 9 将
- Module
的方法添加到
- getModule()
类中,该类返回该类所属的模块。
- Class
类的实例表示一个模块定义,它是从模块声明创建的——通常来自一个 module-info.class 文件。模块描述也可以使用
- ModuleDescriptor
类即时创建。可以使用命令行选项来扩充模块声明,例如
- ModuleDescriptor.Builder
,
- --add-reads
和
- --add-exports
,并使用
- -add-opens
类中的方法,如
- Module
,
- addReads()
和
- addOpens()
。
- addExports()
表示在模块声明时存在的模块描述,而不是增强的模块描述。
- ModuleDescriptor
类的
- Module
方法返回
- getDescriptor()
。
- ModuleDescriptor
是不可变类的。未命名的模块没有模块描述。
- ModuleDescriptor
类的
- Module
方法为未命名的模块返回 null。
- getDescriptor()
类包含几个嵌套类,例如
- ModuleDescriptor
嵌套类;它们每个代表程序中的一个模块语句。
- ModuleDescriptor.Requires
可以使用命令行选项扩充模块描述,并以编程方式使用 Module API。 可以将模块属性的所有查询分为两类:在加载模块后可能会更改的模块的查询和在模块加载后不更改的模块的属性。
类包含第一类中查询的方法,
- Module
类包含第二类中查询的方法。 可以使用
- ModuleDescriptor
类中的
- Module
,
- addExports()
,
- addOpens()
和
- addReads()
方法在运行时更新模块的定义。
- addUses()
可以使用模块声明上的注解。
枚举有
- java.lang.annotation.ElementType
的新值。可以在注解声明上使用
- MODULE
作为目标类型,允许在模块上使用注解类型。在 Java 9 中,两个注解
- MODULE
和
- java.lang.Deprecated
已更新为在模块声明中使用。在模块上使用这些注解只影响模块声明,而不影响模块中包含的类型。
- java.lang.SuppressWarnings
模块安排成层。一个模块层是一组解析的模块,具有将每个模块映射到负责加载该模块中所有类型的类加载器的功能。解析模块的集合称为配置。层次分层排列。层除了空层以外还有至少一个父层,顾名思义,它不含任何模块,主要用作引导层的父层。引导层由启动时由 JVM 创建,通过针对一组可观察模块解析应用程序的初始模块(根模块)。可以创建自定义图层。模块层允许将同一模块的多个版本加载到不同的层中,并在同一个 JVM 中使用。
来源: http://www.cnblogs.com/IcanFixIt/p/7161615.html