Tips
做一个终身学习的人。
十四. 使用模块层
使用模块层是一个高级主题。 典型的Java开发人员不需要直接使用模块层。 现有的应用程序不会使用模块层。 如果将应用程序迁移到JDK 9或使用JDK 9开发新的应用程序,无论是否需要,都至少使用一个由JVM在启动时创建的模块层。 通常,使用插件或容器架构的应用程序将使用模块层。
层是一组解析的模块(一个模块图),具有将每个模块映射到负责加载该模块中所有类型的类加载器的功能。 解析的模块集合称为配置。 可以可视化模块,类加载器,配置和层之间的关系,如下所示:
- Configuration = A module graph
- Module Layer = Configuration + (Module -> Class loader)
模块排列成层。 层次分层排列。 层除了空层以外还有至少一个父层,顾名思义,该层不包含模块,主要存在作为引导层的父层。 引导层由启动时由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
方法来加载服务提供者。 指定的层将是托管的应用程序特定层。 此方法从指定的层及其父层加载服务提供者。 service)
Tips
层是不可变的。 创建图层后,无法向其中添加模块或从中删除模块。 如果需要添加模块或替换其他版本的模块,则必须拆除图层并重新创建。
创建图层是一个多步骤的过程。 需要:
- 创建模块查找器
- 创建一组根模块
- 创建配置对象
- 创建一个图层
创建图层后,可以使用它来加载类型。 将在下一节详细介绍这些步骤。 最后,展示多个版本的模块如何使用图层。
1. 查找模块
模块查找器是ModuleFinder
接口的一个实例。 它用于在模块解析和服务绑定期间查找ModuleReferences
。 该接口包含两种工厂方法来创建模块查找器:
- static ModuleFinder of(Path... entries)
- static ModuleFinder ofSystem()
of()
方法通过搜索指定的路径序列来定位模块,这些路径可以是目录或打包模块的路径。 该方法首先发现模块名称按顺序搜索指定的路径。 以下代码片段显示了如何创建一个在C:\Java9Revealed\lib和C:\Java9Revealed\customLib目录中搜索模块的模块查找器:
// 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()
方法,而不需要任何路径作为参数创建,例如模块查找器。
ofSystem()
方法返回一个模块查找器,它可以查找链接到运行时的系统模块。 该方法始终找到java.base模块。 请注意,可以将自定义的一组模块链接到运行时映像,这意味着使用此方法定位的模块取决于运行时映像。 自定义运行时映像包含JDK模块以及应用程序模块。 该方法将找到两种类型的模块。
还可以使用compose()
方法从零个更多的模块查找器的序列中组成一个模块查找器:
static ModuleFinder compose(ModuleFinder... finders)
该模块查找器将按照指定的顺序使用每个模块查找器。 第二个模块查找器将找到第一个模块查找器未找到的所有模块,第三个模块查找器将找到第一个和第二个模块查找器未找到的所有模块,依此类推。
ModuleFinder
接口包含以下方法来查找模块:
- Optional
find(String name) - Set
findAll()
find()
方法查找具有指定名称的模块。 findAll()
方法查找发现者可以找到的所有模块。
以下包含FindingModule
类的代码,显示如何使用ModuleFinder
。 代码在Windows上使用路径,如C:\Java9Revealed\lib,此目录存储模块。 你可能需要在运行该类之前更改模块路径。 该类是com.jdojo.module.api模块的成员。 可能会得到不同的输出。
// 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 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 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
...
2. 读取模块内容
在上一节中,学习了如何使用ModuleFinder
查找模块引用,它是ModuleReference
类的实例。 ModuleReference
封装了ModuleDescriptor
和模块的位置。 可以使用ModuleReference
类的open()
方法来获取ModuleReader
接口的实例。 ModuleReader
用于列出,查找和读取模块的内容。 以下代码片段显示了如何获取java.base模块的ModuleReader
:
// Create a system module finder
ModuleFinder finder = ModuleFinder.ofSystem();
// The java.base module is guaranteed to exist
Optional omr = finder.find("java.base");
ModuleReference moduleRef = omr.get();
// Get a module reader
ModuleReader reader = moduleRef.open();
ModuleReference
类的open()
方法抛出一个IOException异常。 在这段代码中省略了异常处理,以保持代码简单。
ModuleReader
中的以下方法用于处理模块的内容。 方法名称足够直观地告诉你他们做了什么。
- void close() throws IOException
- Optional
find(String resourceName) throws IOException - Stream
list() throws IOException - default Optional
open(String resourceName) throws IOException - default Optional
read(String resourceName) throws IOException - default void release(ByteBuffer bb)
传递给这些方法的资源名称是“/”分隔的路径字符串。 例如,java.base模块中java.lang.Object类的资源名称为java/lang/Object.class。
一旦完成了使用ModuleReader
,需要使用close()
方法关闭它。 如果尝试使用已经关闭的ModuleReader
读取模块的内容,则会抛出IOException异常。 read()
方法返回一个Optional
。 需要调用release(ByteBuffer bb)
方法来释放字节缓冲区,以避免资源泄漏。
下列包含一个程序,显示如何读取模块的内容。 它读取ByteBuffer
中Object
对象的内容,并以字节为单位打印其大小。 它还在java.base模块中打印五个资源的名称。 你可能会得到不同的输出。
// 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 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 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
4. 创建配置对象
配置表示一组已解析的模块。 解析的模块是一个使用requires
语句指定的依赖关系的模块。 模块解决过程使用两组模块:一组根模块和一组可观察模块。 根模块集合中的每个模块都用作初始模块,其requires
语句针对可观察模块集合进行解析。 根模块可能需要另一个模块,这可能需要另一个模块,等等。 解决过程计算所有根模块的依赖链。 所得到的模块图被称为依赖图。
依赖图只考虑了requires
语句。 如果一个模块使用了requires transitive
语句,则依赖于此模块的模块将隐含地依赖于在必需传递语句中指定的模块。 依赖关系图增加了requires transitive
语句模块的额外可读性,从而产生一个称为可读性图的模块图。
模块中的uses
和provides
语句也构成依赖关系。 如果模块M使用服务类型S,并且另一个模块N提供T的实现S,则模块M依赖于使用服务类型S的模块N。可读性图用针对这样的服务使用依赖性计算的模块进行扩充。
当创建引导层的配置时,它通过解析依赖关系(requires
语句),隐含的可读性(requires transitive
)和服务使用依赖性(uses
和provides
语句)来包含模块。 为用户定义的层创建配置时,可以选择包含或排除服务使用依赖关系。
Configuration
类的实例表示一个配置。 一个配置至少有一个父类,除了一个空配置。
ResolvedModule
类的实例表示配置中已解析的模块。 它的reads()
方法返回一个已解析的模块读取的Set
。 configuration()
方法返回解析的模块是其成员的配置。reference()
方法返回一个ModuleReference
,可以使用它来获取ModuleReader
来读取模块的内容。
Configuration
类中的以下方法创建一个Configuration
对象:
static Configuration empty()
Configuration resolve(ModuleFinder before, ModuleFinder after, Collection roots)
Configuration resolveAndBind(ModuleFinder before, ModuleFinder after, Collection roots)
static Configuration resolve(ModuleFinder before, List parents, ModuleFinder after, Collection roots)
static Configuration resolveAndBind(ModuleFinder before, List parents, ModuleFinder after, Collection 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 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 findModule(String name)
Set modules()
List parents()
这些方法的名称和签名是直观的,足以理解它们的使用。 在下一节中,介绍如何使用配置来创建模块层。
5. 创建模块层
模块层是将每个模块映射到类加载器的配置和功能。 要创建一个图层,必须先创建一个配置,并有一个或多个类加载器将模块映射到它们。 模块的类加载器负责加载该模块中的所有类型。 可以将配置中的所有模块映射到一个类加载器;也可以将每个模块映射到不同的类加载器;或者可以有自定义映射策略。 通常,类加载器使用委派策略来将类加载请求委托给其父类加载器。 当为层中的模块定义类加载器时,也可以使用此策略。
java.lang包中的ModuleLayer
类的实例代表一个模块层。 该类包含两个方法,empty()
和boot()
,它们分别返回一个空配置的空层和引导层。 类中的以下方法用于创建自定义图层:
ModuleLayer defineModules(Configuration cf, Function clf)
static ModuleLayer.Controller defineModules(Configuration cf, List parentLayers, Function clf)
ModuleLayer defineModulesWithManyLoaders(Configuration cf, ClassLoader parentClassLoader)
static ModuleLayer.Controller defineModulesWithManyLoaders(Configuration cf, List parentLayers, ClassLoader parentLoader)
ModuleLayer defineModulesWithOneLoader(Configuration cf, ClassLoader parentClassLoader)
static ModuleLayer.Controller defineModulesWithOneLoader(Configuration cf, List parentLayers, ClassLoader parentLoader)
defineModulesXxx()
方法有两个变体:一个集合包含实例方法,另一个集合包含静态方法。 实例方法使用它们被称为父层的层,而静态方法可以指定新层的父层列表。 静态方法返回一个ModuleLayer.Controller
对象,可以使用它来处理新层中的模块。 ModuleLayer.Controller
是java.lang包中的一个嵌套类,具有以下方法:
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
方法将配置作为其第一个参数。 第二个参数是映射函数,它在配置中获取模块名,并为该模块返回类加载器。 方法调用可能会失败,如果:
- 具有相同包的多个模块映射到同一个类加载器。
- 一个模块被映射到定义相同名称的模块的类加载器。
- 模块被映射到已经在模块中的任何包中定义了类型的类加载器。
defineModulesWithManyLoaders(Configuration cf, ClassLoader parentClassLoader)
方法使用指定的配置创建一个模块层。 配置中的每个模块都映射到由此方法创建的不同类加载器。 指定的父类加载器(第二个参数)被设置为通过此方法创建的类加载器的父级。 通常,使用应用程序类加载器作为由此方法创建的所有类加载器的父类加载器。 可以使用null作为第二个参数来使用引导类加载器作为由此方法创建的所有类加载器的父级。 该方法将为配置中的每个模块创建一个新的类加载器。
defineModulesWithOneLoader(Configuration cf, ClassLoader parentClassLoader)
方法使用指定的配置创建一个模块层。 它使用指定的父类加载器作为其父类创建一个类加载器。 它将配置中的所有模块映射到该类加载器。 可以使用null作为第二个参数来使用引导类加载器作为由此方法创建的所有类加载器的父级。
以下代码段创建一个层,引导层作为其父层。 层中的所有模块将由一个类加载器加载,父类是系统类加载器。
Configuration config = /* create a configuration... */
ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
ModuleLayer parentLayer = ModuleLayer.boot();
ModuleLayer layer = parentLayer.defineModulesWithOneLoader(config, sysClassLoader);
创建图层后,需要从该图层中的模块加载类。 模块中的所有类型都由映射到该模块的类加载器加载。 请注意,可能在多个层中定义了相同的模块,但这些模块将被映射到不同的类加载器。 ModuleLayer
类包含一个findLoader(String moduleName)
方法,它接受模块名称作为参数,并返回该模块的类加载器。 如果模块未在层中定义,则会检查父层。 如果模块不存在于此层或其祖先层中,则会抛出IllegalArgumentException异常。 一旦获得了模块的类加载器,可以调用它的`loadClass(String className)方法从该模块加载一个类。 以下代码片段(不包括异常处理逻辑)显示了如何在图层中加载类:
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 findModule(String moduleName)
Set modules()
List parents()
findModule()
方法在层或其父层中查找具有指定名称的模块。 modules()
方法返回层中的一组模块,如果该层不包含任何模块,那么它可能是一个空集合。parent()
方法返回此图层的父层列表,如果是空层则为空。
接下来,介绍如何创建自定义层的完整示例,以及如何在同一应用程序中将两个版本的同一模块加载到两个层中。
模块名称是com.jdojo.layer,它由一个名为com.jdojo.layer的包,它只包含一个名为LayerInfo
的类。 有两个版本的相同模块,所以一切都将重复。 在源代码中创建了两个名为com.jdojo.layer.v1和com.jdojo.layer.v2的NetBeans项目。
下面包含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版本和LayerInfo
类的类声明。 只有一件事情从这个模块的版本1.0改为版本2.0,静态变量VERSION的值从1.0变为2.0。
// 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 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 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()
方法声明CUSTOM_MODULE_LOCATION
的变量,它b保存com.jdojo.layer模块2.0版本的位置。 必须将路径更改为指向计算机上包含com.jdojo.layer模块版本2.0的编译模块代码的目录。
final String CUSTOM_MODULE_LOCATION = "C:\\Java9Revealed\\customLib";
下面代码保存com.jojo.layer作为自定义图层配置的唯一根模块:
Set rootModules = Set.of("com.jdojo.layer");
调用createLayer()
方法来创建自定义模块层。 该方法使用逻辑在CUSTOM_MODULE_LOCATION
创建com.jdojo.layer模块版本2.0的自定义层:
ModuleLayer customLayer = createLayer(CUSTOM_MODULE_LOCATION, rootModules);
main()
方法获取引导层的引用:
ModuleLayer bootLayer = ModuleLayer.boot();
现在,testLayer()
方法被调用一次用于引导层,一次用于自定义层。 该方法在模块层中找到com.jdojo.layer模块的类加载器,并加载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
和LayerInstantiationException
类在java.lang包中,其余的在java.lang.module包中。
Module
类的实例代表运行时模块。 加载到JVM中的每个类型都属于一个模块。 JDK 9将getModule()
的方法添加到Class
类中,该类返回该类所属的模块。
ModuleDescriptor
类的实例表示一个模块定义,它是从模块声明创建的——通常来自一个module-info.class文件。模块描述也可以使用ModuleDescriptor.Builder
类即时创建。可以使用命令行选项来扩充模块声明,例如--add-reads
,--add-exports
和-add-opens
,并使用Module
类中的方法,如addReads()
,addOpens()
和addExports()
。 ModuleDescriptor
表示在模块声明时存在的模块描述,而不是增强的模块描述。 Module
类的getDescriptor()
方法返回ModuleDescriptor
。 ModuleDescriptor
是不可变类的。未命名的模块没有模块描述。 Module
类的getDescriptor()
方法为未命名的模块返回null。 ModuleDescriptor
类包含几个嵌套类,例如ModuleDescriptor.Requires
嵌套类;它们每个代表程序中的一个模块语句。
可以使用命令行选项扩充模块描述,并以编程方式使用Module API。 可以将模块属性的所有查询分为两类:在加载模块后可能会更改的模块的查询和在模块加载后不更改的模块的属性。Module
类包含第一类中查询的方法,ModuleDescriptor
类包含第二类中查询的方法。
可以使用Module
类中的addExports()
,addOpens()
,addReads()
和addUses()
方法在运行时更新模块的定义。
可以使用模块声明上的注解。 java.lang.annotation.ElementType
枚举有MODULE
的新值。可以在注解声明上使用MODULE
作为目标类型,允许在模块上使用注解类型。在Java 9中,两个注解java.lang.Deprecated
和java.lang.SuppressWarnings
已更新为在模块声明中使用。在模块上使用这些注解只影响模块声明,而不影响模块中包含的类型。
模块安排成层。一个模块层是一组解析的模块,具有将每个模块映射到负责加载该模块中所有类型的类加载器的功能。解析模块的集合称为配置。层次分层排列。层除了空层以外还有至少一个父层,顾名思义,它不含任何模块,主要用作引导层的父层。引导层由启动时由JVM创建,通过针对一组可观察模块解析应用程序的初始模块(根模块)。可以创建自定义图层。模块层允许将同一模块的多个版本加载到不同的层中,并在同一个JVM中使用。