java10 api使用_Java 9 揭秘(10. 模块API)

Tips

做一个终身学习的人。

d0dcf7682bd7?open_source=weibo_search

Java 9

在本章节中,主要介绍以下内容:

什么是模块 API

如何在程序中表示模块和模块描述

如何读取程序中的模块描述

如何表示模块的版本

如何使用Module和ModuleDescriptor类读取模块的属性

如何使用Module类在运行时更新模块的定义

如何创建可用于模块的注解以及如何读取模块上使用的注解

什么是模块层和配置

如何创建自定义模块层并将模块加载到它们中

一. 什么是模块API

模块API由可以让你对模块进行编程访问的类和接口组成。 使用API,可以通过编程方式:

读取,修改和构建模块描述符

加载模块

读取模块的内容

搜索加载的模块

创建新的模块层

模块API很小。 它由大约15个类和接口组成,分布在两个包中:

java.lang

java.lang.module

Module,ModuleLayer和LayerInstantiationException类在java.lang包中,其余的在java.lang.module包中。 下表包含模块API中的类的列表,每个类的简要说明。 列表未排序。 首先列出了Module和ModuleDescriptor,因为应用程序开发人员最常使用它们。 所有其他类通常由容器和类库使用。 该列表不包含Module API中的异常类。

描述

Module

表示运行时模块。

ModuleDescriptor

表示模块描述。 这是不可变类。

ModuleDescriptor.Builder

用于以编程方式构建模块描述的嵌套构建器类。

ModuleDescriptor.Exports

表示模块声明中的exports语句的嵌套类。

ModuleDescriptor.Opens

表示模块声明中的opens语句的嵌套类。

ModuleDescriptor.Provides

表示模块声明中的provides语句的嵌套类。

ModuleDescriptor.Requires

表示模块声明中的requires语句的嵌套类。

ModuleDescriptor.Version

表示模块版本字符串的嵌套类。 它包含一个从版本字符串返回其实例的parse(String v)工厂方法。

ModuleDescriptor.Modifier

枚举类,其常量表示在模块声明中使用的修饰符,例如打开模块的OPEN。

ModuleDescriptor.Exports.Modifier

枚举类,其常量表示在模块声明中用于exports语句的修饰符。

ModuleDescriptor.Opens.Modifier

枚举类,其常量表示在模块声明中的opens语句上使用的修饰符。

ModuleDescriptor.Requires.Modifier

枚举类,其常量表示在模块声明中的requires语句上使用的修饰符。

ModuleReference

模块的内容的引用。 它包含模块的描述及其位置。

ResolvedModule

表示模块图中已解析的模块。 包含模块的名称,其依赖关系和对其内容的引用。 它可以用于遍历模块图中模块的所有传递依赖关系。

ModuleFinder

用于在指定路径或系统模块上查找模块的接口。 找到的模块作为ModuleReference的实例返回。 它包含工厂方法来获取它的实例。

ModuleReader

用于读取模块内容的接口。 可以从ModuleReference获取ModuleReader。

Configuration

表示解析模块的模块图。

ModuleLayer

包含模块图(Configuration)以及模块图中的模块与类加载器之间的映射。

ModuleLayer.Controller

用于控制ModuleLayer中的模块的嵌套类。 ModuleLayer类中的方法返回此类的实例。

二. 表示模块

Module类的实例代表一个运行时模块。 加载到JVM中的每个类型都属于一个模块。JDK 9在Class类中添加了一个名为getModule()的方法,该类返回该类所属的模块。 以下代码片段显示了如何获取BasicInfo的类的模块:

// Get the Class object for of the BasicInfo class

Class cls = BasicInfo.class;

// Get the module reference

Module module = cls.getModule();

模块可以是命名或未命名的。 Module类的isNamed()方法对于命名模块返回true,对于未命名的模块返回false。

每个类加载器都包含一个未命名的模块,其中包含类加载器从类路径加载的所有类型。 如果类加载器从模块路径加载类型,则这些类型属于命名模块。 Class类的getModule()方法可能会返回一个命名或未命名的模块。 JDK 9将一个名为getUnnamedModule()的方法添加到ClassLoader类中,该类返回类加载器的未命名模块。 在下面的代码片段中,假设BasicInfo类是从类路径加载的,m1和m2指的是同一个模块:

Class cls = BasicInfo.class;

Module m1 = cls.getClassLoader().getUnnamedModule();

Module m2 = cls.getModule();

Module类的getName()方法返回模块的名称。 对于未命名的模块,返回null。

// Get the module name

String moduleName = module.getName();

Module类中的getPackages()方法返回包含模块中所有包的Set类型。getClassLoader()方法返回模块的类加载器。

getLayer()方法返回包含该模块的ModuleLayer; 如果模块不在图层中,则返回null。 模块层仅包含命名模块。 所以,这个方法总是为未命名的模块返回null。

三. 描述模块

ModuleDescriptor类的实例表示一个模块定义,它是从一个模块声明创建的 —— 通常来自module-info.class文件。 模块描述也可以使用ModuleDescriptor.Builder类创建。 可以使用命令行选项来扩充模块声明,例如--add-reads,--add-exports和-add-opens,并使用Module类中的方法,如addReads(),addOpens()和addExports()。 ModuleDescriptor表示在模块声明时添加的模块描述,而不是增强的模块描述。 Module类的getDescriptor()方法返回一个ModuleDescriptor:

Class cls = BasicInfo.class;

Module module = cls.getModule();

// Get the module descriptor

ModuleDescriptor desc = module.getDescriptor();

Tips

ModuleDescriptor是不可变的。 未命名的模块没有模块描述。 Module类的getDescriptor()方法为未命名的模块返回null。

还可以使用ModuleDescriptor类的静态read()方法从module-info.class文件读取模块声明的二进制形式来创建一个ModuleDescriptor对象。 以下代码片段从当前目录中读取一个module-info.class文件。 为清楚起见排除异常处理:

String moduleInfoPath = "module-info.class";

ModuleDescriptor desc = ModuleDescriptor.read(new FileInputStream(moduleInfoPath));

四. 表示模块声明

ModuleDescriptor类包含以下静态嵌套类,其实例表示模块声明中具有相同名称的语句:

ModuleDescriptor.Exports

ModuleDescriptor.Opens

ModuleDescriptor.Provides

ModuleDescriptor.Requires

请注意,没有ModuleDescriptor.Uses类来表示uses语句。 这是因为uses语句可以表示为String的服务接口名称。

五. 表示exports语句

ModuleDescriptor.Exports类的实例表示模块声明中的exports语句。 类中的以下方法返回导出语句的组件:

boolean isQualified()

Set modifiers()

String source()

Set targets()

isCualified()方法对于限定的导出返回true,对于非限定的导出,返回false。 source()方法返回导出的包的名称。 对于限定的导出,targets()方法返回一个不可变的模块名称set类型,导出该包,对于非限定的导出,它返回一个空的set。 modifiers()方法返回一系列exports语句的修饰符,它们是ModuleDescriptor.Exports.Modifier枚举的常量,它包含以下两个常量:

MANDATED:源模块声明中的exports隐式声明。

SYNTHETIC:源模块声明中的exports未明确或隐含地声明。

六. 表示opens语句

ModuleDescriptor.Opens类的实例表示模块声明中的一个opens语句。 类中的以下方法返回了opens语句的组件:

boolean isQualified()

Set modifiers()

String source()

Set targets()

isCualified()方法对于限定的打开返回true,对于非限定打开,返回false。source()方法返回打开包的名称。 对于限定的打开,targets()方法返回一个不可变的模块名称set类型,打开该包,对于非限定打开,它返回一个空set。 该modifiers()方法返回一系列的opens语句,它们是嵌套的ModuleDescriptor.Opens.Modifier枚举的常量,它包含以下两个常量:

MANDATED:源模块声明的中的opens隐式声明。

SYNTHETIC:源模块声明中的opens未明确或隐含地声明。

七. 表示provides语句

ModuleDescriptor.Provides类的实例表示模块声明中特定服务类型的一个或多个provides语句。 以下两个provides语句为相同的服务类型X.Y指定两个实现类:

provides X.Y with A.B;

provides X.Y with Y.Z;

ModuleDescriptor.Provides类的实例将代表这两个语句。 类中的以下方法返回了provides语句的组件:

List providers()

String service()

providers()方法返回提供者类的完全限定类名的列表。 在上一个示例中,返回的列表将包含A.B和Y.Z。service()方法返回服务类型的全限定名称。 在前面的例子中,它将返回X.Y.

八. 表示requires语句

ModuleDescriptor.Requires类的实例表示模块声明中的requires语句。 类中的以下方法返回requires语句的组件:

Optional compiledVersion()

Optional rawCompiledVersion()

String name()

Set modifiers()

假设一个名为M的模块有一个requires N语句被编译。如果N的模块版本在编译时可用,则该版本将记录在M的模块描述中。compiledVersion()方法返回N中的Optional版本。如果N的版本没有可用,则该方法返回一个空可选。在requires语句中指定的模块的模块版本仅在信息方面被记录在模块描述中。模块系统在任何阶段都不使用它。但是,它可以被工具和框架用于诊断目的。例如,一个工具可以验证使用requires语句指定为依赖关系的所有模块必须具有与编译期间记录的相同或更高版本的版本。

继续前一段中的示例,rawCompiledVersion()方法返回Optional中的模块N的版本。在大多数情况下,compileVersion()和rawCompiledVersion()的两个方法将返回相同的模块版本,但是可以以两种不同的格式返回:一个Optional对象,另一个Optional对象。可以拥有一个模块版本无效的模块。这样的模块可以在Java模块系统之外创建和编译。可以将具有无效模块版本的模块加载为Java模块。在这种情况下,compileVersion()方法返回一个空的Optional,因为模块版本不能被解析为有效的Java模块版本,而rawCompiledVersion()返回一个包含无效模块版本的Optional 。

Tips

ModuleDescriptor.Requires类的rawCompiledVersion()方法可能返回所需的模块的不可解析版本。

name()方法返回在requires语句中指定的模块的名称。 modifiers()方法返回的是requires语句的一组修饰符,它们是嵌套的ModuleDescriptor.Requires.Modifier枚举的常量,它包含以下常量:

MANDATED:在源模块声明的中的依赖关系的隐式声明。

STATIC:依赖关系在编译时是强制性的,在运行时是可选的。

SYNTHETIC:在源模块声明中依赖关系的未明确或隐含地声明。

TRANSITIVE:依赖关系使得依赖于当前模块的任何模块都具有隐含声明的依赖于该requires语句命名的模块。

1. 代表模块版本

ModuleDescriptor.Version类的实例表示一个模块的版本。 它包含一个名为parse(String version)的静态工厂方法,返回其表示指定版本字符串中的版本的实例。 回想一下,你不要在模块的声明中指定模块的版本。 当你将模块代码打包到模块化JAR(通常使用jar工具)时,可以添加模块版本。 javac编译器还允许在编译模块时指定模块版本。

模块版本字符串包含三个组件:

强制版本号

可选的预发行版本

可选构建版本

模块版本具有以下形式:

vNumToken+ ('-' preToken+)? ('+' buildToken+)?

每个组件是一个token序列;每个都是非负整数或一个字符串。 token由标点符号“,”,“-” 或“+”或从数字序列转换为既不是数字也不是标点符号的字符序列,反之亦然。版本字符串必须以数字开头。 版本号是由一系列由“."分隔token序列组成。 以第一个“-”或“+”字符终止。 预发行版本是由一系列由“.”或“-”分隔token序列组成。 以第一个“+”字符终止。 构建版本是由“.”,“,”,“-”或“+”字符分隔的token序列。

ModuleDescriptor类的version()方法返回Optional。

2. 模块的其他属性

在包装模块化JAR时,还可以在module-info.class文件中设置其他模块属性,如如主类名,操作系统名称等。ModuleDescriptor类包含返回每个这样的属性的方法。ModuleDescriptor类中包含以下令人感兴趣的方法:

Set exports()

boolean isAutomatic()

boolean isOpen()

Optional mainClass()

String name()

Set opens()

Set packages()

Set provides()

Optional rawVersion()

Set requires()

String toNameAndVersion()

Set uses()

方法名称很直观,以便了解其目的。 下面这两个方法,需要一些解释:packages()和provide()。

ModuleDescriptor类包含一个名为packages()的方法,Module类包含一个名为getPackages()的方法。 两者都返回包名的集合。 为什么为了同一目的有两种方法? 事实上,它们有不同的用途。 在ModuleDescripto中,该方法返回在模块声明中定义的包名的集合,无论它们是否被导出。 回想一下,你无法获得一个未命名模块的ModuleDescriptor,在这种情况下,可以使用Module类中的getPackages()方法在未命名模块中获取软件包名称。 另一个区别是ModuleDescriptor记录的包名是静态的;Module记录的包名称是动态的,它记录在调用getPackages()方法时在模块中加载的包。 模块记录在运行时当前加载的所有包。

provides()方法返回Set,考虑在模块声明中以下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的的列表。

3. 了解模块基本信息

在本节中,将展示如何在运行时读取有关模块的基本信息的示例。 下面包含名为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 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 currentPackages = m.getPackages();

System.out.printf("Packages: %s%n", currentPackages);

return;

}

Set requires = desc.requires();

Set exports = desc.exports();

Set uses = desc.uses();

Set provides = desc.provides();

Set 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类被加载到应用程序类加载器的未命名模块中,这反映在为两个模块isNamed()方法返回false。 注意Module类的getPackages()方法的动态特性。 当第一次调用它时,它只返回一个包名称com.jdojo.module.api。 当它第二次被调用时,它返回两个包名称com.jdojo.module.api和com.jdojo.prime。 这是因为未命名模块中的包是从新的包中添加的类型加载到未命名的模块中。 在这两种情况下,java.sql模块的输出保持不变,因为平台类型始终加载到同一模块中,而与运行java启动的模式无关。

九. 查询模块

针对模块运行的典型查询包括:

模块M可以读另一个模块N吗?

模块可以使用特定类型的服务吗?

模块是否将特定包导出到所有或某些模块?

一个模块是否打开一个特定的包到所有或一些模块?

这个模块是命名还是未命名模块?

这是一个自动命名模块吗?

这是一个开放模块吗?

可以使用命令行选项扩充模块描述,并以编程方式使用Module API。 可以将模块属性的所有查询分为两类:在加载模块后,其结果可能会更改的查询,以及在模块加载后其结果不会更改的查询。 Module类包含第一类中查询的方法,ModuleDescriptor类包含第二类中查询的方法。Module类为第一类中的查询提供了以下方法:

boolean canRead(Module other)

boolean canUse(Class> service)

boolean isExported(String packageName)

boolean isExported(String packageName, Module other)

boolean isOpen(String packageName)

boolean isOpen(String packageName, Module other)

boolean isNamed()

方法名称直观足够告诉你他们做了什么。 isNamed()方法对于命名模块返回true,对于未命名的模块返回false。 名称或未命名的模块类型在模块加载完成后不会更改。 此方法在Module类中提供,因为无法获取未命名模块的ModuleDescriptor。

ModuleDescriptor包含三种方法来告诉你模块的类型以及模块描述符的生成方式。 如果isOpen()方法是一个打开的模块,则返回true,否则返回false。isAutomatic()方法对于自动命名模块返回true,否则返回false。

下面包含名QueryModule类的代码,它是com.jdojo.module.api模块的成员。 它显示如何查询模块的依赖关系检查,以及软件包是导出还是打开到所有模块或仅对特定模块。

// QueryModule.java

package com.jdojo.module.api;

import java.sql.Driver;

public class QueryModule {

public static void main(String[] args) throws Exception {

Class 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类包含以下方法,可以在运行时修改模块声明:

Module addExports(String packageName, Module other)

Module addOpens(String packageName, Module other)

Module addReads(Module other)

Module addUses(Class> serviceType)

使用命令行选项和上面的种方法来修改模块的声明有很大的区别。 使用命令行选项,可以修改任何模块的声明。 然而,这些方法是调用者敏感的。 调用这些方法的代码必须在声明被修改的模块中,除了调用addOpens()方法。 也就是说,如果无法访问模块的源代码,则无法使用这些方法来修改该模块的声明。 这些方法通常被框架使用,可以适应运行时需要与其他模块交互。

所有这些方法在处理命名模块时都会抛出IllegalCallerException,因此调用者不允许调用这些模块。

addExports()方法更新模块以将指定的包导出到指定的模块。 如果指定的包已经导出或打开到指定的模块,或者在未命名或打开的模块上调用该方法,则调用该方法将不起作用。 如果指定的包为空或模块中不存在,则抛出IllegalArgumentException异常。 调用此方法与向模块声明中添加限定导出具有相同的效果:

exports to ;

addOpens()方法与addExports()方法工作方式相同,只是它更新模块以将指定的包打开到指定的模块。 它类似于在模块中添加以下语句:

opens to ;

addOpens()方法对关于谁可以调用此方法的规则会产生异常。 可以从同一模块的代码调用其他方法。 但是,可以从另一个模块的代码调用一个模块的addOpens()方法。 假设模块M使用以下声明将软件包P对模块N开放:

module M {

opens P to N;

}

在这种情况下,模块N被允许调用模块M上的addOpens(“P”, S)方法,这允许模块N将软件包P打开到模块S。当模块的作者可以将模块的包打开到已知的抽象框架模块时,在模块运行时发现并使用另一个实现模块。动态已知的模块都可能需要对所声明的模块进行深层反射访问。在这种情况下,模块的作者只需要了解抽象框架的模块名称并打开它的包。在运行时,抽象框架的模块可以打开与动态发现的实现模块相同的包。考虑JPA作为一个抽象框架,定义了一个java.persistence模块,并在运行时发现了其他JPA实现,如Hibernate和EclipseLink。在这种情况下,模块的作者只能打开一个包到java.persistence模块,该模块可以在运行时打开与Hibernate或EclipseLink模块相同的软件包。

addReads()方法将可读性边界从该模块添加到指定的模块。 如果指定的模块本身是因为每个模块都可以读取自身或者由于未命名模块可以读取所有模块而在未命名模块上被调用,则此方法无效。 调用此方法与requires语句添加到模块声明中的作用相同:

requires ;

addUses()方法更新模块以添加服务依赖关系,因此可以使用ServiceLoader类来加载指定服务类型的服务。 在未命名或自动命名模块上调用时不起作用。 其效果与在模块声明中添加以下uses语句相同:

uses ;

下面包含UpdateModule类的代码。 它在com.jdojo.module.api模块中。 请注意,模块声明不包含uses语句。 该类包含一个findFirstService()方法,它接受一个服务类型作为参数。 它检查模块是否可以加载服务类型。 回想一下,模块必须包含具有指定服务类型的uses语句,以使用ServiceLoader类加载该服务类型。 该方法使用Module类的addUses()方法,如果不存在,则为该服务类型添加一个uses语句。 最后,该方法加载并返回加载的第一个服务提供者。

// UpdateModule.java

package com.jdojo.module.api;

import java.util.ServiceLoader;

public class UpdateModule {

public static T findFirstService(Class 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类的findFirstService()方法。 下面包含名为com.jdojo.module.api.test的模块的声明。 它声明对com.jdojo.prime模块的依赖,因此它可以使用PrimeChecker服务类型接口。 它声明对com.jdojo.module.api模块的依赖,因此它可以使用UpdateModule类加载服务。 需要将这两个模块添加到NetBeans中com.jdojo.module.api.test模块的模块路径中。

// 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的新值。 如果在注解声明中使用MODULE作为目标类型,则允许在模块上使用注解。 在Java 9中,两个注释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();

}

下面包含了一个AnnotationTest类的代码。 它读取com.jdojo.module.api.annotation模块上的注解。 输出不包含模块上存在的@SuppressWarnings注解,因为此注解使用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()方法来加载和初始化一个类:

Class> forName(String className) throws ClassNotFoundException

Class> forName(String className, boolean initialize, ClassLoader loader) throws ClassNotFoundException

Class> forName(Module module, String className)

在这些方法中,className参数是要加载的类或接口的完全限定名称,例如java.lang.Thread和com.jdojo.intro.Welcome。 如果initialize参数为true,则该类将被初始化。

The forName(String className)方法在加载之后初始化该类,并使用当前的类加载器,该加载器是加载调用此方法的类的类加载器。 表达式Class.forName("P.Q")里的实例方法相当于Class.forName("P.Q", true, this.getClass().getClassLoader()),

下面包含作为com.jdojo.module.api模块成员的LoadClass类的代码。 该类包含两个版本的loadClass()方法。 该方法加载指定的类,并且在成功加载类之后,它尝试使用无参构造函数来实例化该类。 请注意,com.jdojo.intro模块不导出包含Welcome类的com.jdojo.intro包。 此示例尝试加载和实例化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 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.Welcome类。 但是,我们无法将其实例化,因为它不会导出到com.jdojo.intro模块中。 以下命令使用--add-exports选项将com.jdojo.intro模块中的com.jdojo.intro包导出到com.jdojo.module.api模块。 输出显示我们可以加载并实例化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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值