1. 概述

继《Java 9 模块化指南》之后,本文将深入探讨随 Java 平台模块系统(JPMS)引入的 java.lang.Module API。

这个 API 提供了在运行时以编程方式访问模块的能力,可以获取模块的详细信息,比如名称、依赖、导出包等,并对其进行动态操作。核心是通过 Module 和其关联的 ModuleDescriptor 来实现对模块元数据的读取与修改。

2. 读取模块信息

Module 类代表了命名模块(named module)和未命名模块(unnamed module)。

  • 命名模块:拥有明确名称,由 JVM 在构建模块层(Module Layer)时,根据模块图(module graph)创建。
  • 未命名模块:没有名称,每个 ClassLoader 都对应一个未命名模块。所有不属于任何命名模块的类,都属于其类加载器关联的未命名模块。

Module 的价值在于它提供了一系列方法,让我们能动态获取模块的元信息,比如模块名、类加载器、包含的包等。

2.1 判断命名模块 vs 未命名模块

通过 isNamed() 方法可以轻松判断一个类所属的模块类型。

HashMap 为例,它属于 Java 核心模块:

Module javaBaseModule = HashMap.class.getModule();

assertThat(javaBaseModule.isNamed(), is(true));
assertThat(javaBaseModule.getName(), is("java.base"));

再看一个我们自己定义的普通类 Person

public class Person {
    private String name;

    // constructor, getters and setters
}

检查其模块信息:

Module module = Person.class.getModule();

assertThat(module.isNamed(), is(false));
assertThat(module.getName(), is(nullValue()));

⚠️ 踩坑提醒:在传统的 classpath 下编译的类,即使你用了 module-info.java,如果没正确打包成模块 JAR,它们依然会落在未命名模块里,isNamed() 返回 false

2.2 获取模块中的包

想知道某个模块包含了哪些包?用 getPackages() 方法:

assertTrue(javaBaseModule.getPackages().contains("java.lang.annotation"));
assertFalse(javaBaseModule.getPackages().contains("java.sql"));

这个集合包含了该模块声明导出(exports)和自动导出的所有包。

2.3 获取模块注解

模块也可以有注解,比如 @Deprecated。使用 getAnnotations() 可以获取:

assertThat(javaBaseModule.getAnnotations().length, is(0));
  • ✅ 命名模块:如果没有注解,返回空数组。
  • ✅ 未命名模块:调用此方法也返回空数组。

目前标准模块很少使用注解,但自定义模块可以利用这一点做标记。

2.4 获取类加载器

通过 getClassLoader() 可以拿到加载该模块的 ClassLoader

assertThat(
  module.getClassLoader().getClass().getName(), 
  is("jdk.internal.loader.ClassLoaders$AppClassLoader")
);

这在调试类加载问题时非常有用,能清晰看到模块与类加载器的对应关系。

2.5 获取模块层(ModuleLayer)

ModuleLayer 是模块系统的核心概念之一,它代表了 JVM 中模块的一层视图。

  • ✅ 模块层告诉 JVM 哪些类可以从哪些模块加载,从而精确控制类的归属。
  • ✅ 它包含配置信息、父层引用以及该层中所有模块的集合。

获取模块所属的层:

ModuleLayer javaBaseModuleLayer = javaBaseModule.getLayer();

进一步查询层的信息:

assertTrue(javaBaseModuleLayer.configuration().findModule("java.base").isPresent());

⚠️ 特别说明:JVM 启动时会创建一个特殊的 boot layer(启动层),它是唯一包含 java.base 模块的层,其他层通常以它为父层。

3. 深入 ModuleDescriptor

ModuleDescriptor 是模块的“身份证”,它描述了一个命名模块的静态元数据,包括依赖、导出、服务提供等。它是不可变对象,线程安全。

3.1 获取 ModuleDescriptor

最直接的方式是从 Module 对象获取:

ModuleDescriptor moduleDescriptor = javaBaseModule.getDescriptor();

3.2 创建 ModuleDescriptor

除了读取,你还可以通过 ModuleDescriptor.Builder 动态创建一个描述符:

ModuleDescriptor.Builder moduleBuilder = ModuleDescriptor
  .newModule("baeldung.base");

ModuleDescriptor moduleDescriptor = moduleBuilder.build();

assertThat(moduleDescriptor.name(), is("baeldung.base"));
  • newModule():创建普通模块。
  • newOpenModule():创建开放模块(open module),所有包对反射开放。
  • newAutomaticModule():创建自动模块(automatic module),用于兼容老的 JAR 包。

3.3 判断模块类型

通过 ModuleDescriptor 可以判断模块是普通、开放还是自动模块:

ModuleDescriptor moduleDescriptor = javaBaseModule.getDescriptor();

assertFalse(moduleDescriptor.isAutomatic());
assertFalse(moduleDescriptor.isOpen());

3.4 获取依赖(requires)

requires() 方法返回模块的依赖列表(Set<Requires>):

Set<Requires> javaBaseRequires = javaBaseModule.getDescriptor().requires();
Set<Requires> javaSqlRequires = javaSqlModule.getDescriptor().requires();

Set<String> javaSqlRequiresNames = javaSqlRequires.stream()
  .map(Requires::name)
  .collect(Collectors.toSet());

assertThat(javaBaseRequires, empty());
assertThat(javaSqlRequiresNames, hasItems("java.base", "java.xml", "java.logging"));

关键点:除 java.base 外,所有模块都隐式依赖 java.base。自动模块的依赖集通常只包含 java.base

3.5 获取服务提供(provides)

provides() 返回模块提供的服务实现:

Set<Provides> javaBaseProvides = javaBaseModule.getDescriptor().provides();
Set<Provides> javaSqlProvides = javaSqlModule.getDescriptor().provides();

Set<String> javaBaseProvidesService = javaBaseProvides.stream()
  .map(Provides::service)
  .collect(Collectors.toSet());

assertThat(javaBaseProvidesService, hasItem("java.nio.file.spi.FileSystemProvider"));
assertThat(javaSqlProvides, empty());

例如 java.base 提供了文件系统 SPI 的实现。

3.6 获取导出包(exports)

exports() 返回模块对外导出的包:

Set<Exports> javaSqlExports = javaSqlModule.getDescriptor().exports();

Set<String> javaSqlExportsSource = javaSqlExports.stream()
  .map(Exports::source)
  .collect(Collectors.toSet());

assertThat(javaSqlExportsSource, hasItems("java.sql", "javax.sql"));

⚠️ 自动模块不会显式导出包,其 exports() 返回空集。

3.7 获取服务使用(uses)

uses() 返回模块所依赖的服务接口:

Set<String> javaSqlUses = javaSqlModule.getDescriptor().uses();

assertThat(javaSqlUses, hasItem("java.sql.Driver"));

这对应 module-info.java 中的 uses java.sql.Driver; 语句。

⚠️ 自动模块的 uses() 集合为空。

3.8 获取开放包(opens)

opens() 返回模块通过 opens 关键字开放给反射的包:

Set<Opens> javaBaseUses = javaBaseModule.getDescriptor().opens();
Set<Opens> javaSqlUses = javaSqlModule.getDescriptor().opens();

assertThat(javaBaseUses, empty());
assertThat(javaSqlUses, empty());
  • ✅ 普通模块:只有显式 opens 的包才会出现在这里。
  • ✅ 开放模块(open module):所有包都对反射开放,但 opens() 返回空,因为它是模块级别的开放。

4. 动态操作模块

除了读取,Module API 还允许在运行时动态修改模块的可访问性,这在框架或测试中非常有用。

4.1 添加导出(addExports)

让当前模块将某个包导出给另一个模块:

Module updatedModule = module.addExports(
  "com.baeldung.java9.modules", javaSqlModule);

assertTrue(updatedModule.isExported("com.baeldung.java9.modules"));
  • ✅ 仅当调用者的模块是代码所属模块时才有效。
  • ⚠️ 如果包已导出或目标模块是开放模块,则无效果。

4.2 添加读取权限(addReads)

让当前模块读取另一个模块:

Module updatedModule = module.addReads(javaSqlModule);

assertTrue(updatedModule.canRead(javaSqlModule));
  • ✅ 模块默认可以读自己,所以给自己加读取无效。
  • ⚠️ 未命名模块或已存在读取关系时,调用无效果。

4.3 添加开放包(addOpens)

将某个包对另一个模块开放(用于反射):

Module updatedModule = module.addOpens(
  "com.baeldung.java9.modules", javaSqlModule);

assertTrue(updatedModule.isOpen("com.baeldung.java9.modules", javaSqlModule));
  • ✅ 类似 --add-opens JVM 参数的运行时版本。
  • ⚠️ 如果包已对目标模块开放,则无效果。

4.4 添加服务依赖(addUses)

动态声明模块使用某个服务:

Module updatedModule = module.addUses(Driver.class);

assertTrue(updatedModule.canUse(Driver.class));
  • ⚠️ 对未命名模块或自动模块调用此方法无效。

5. 总结

本文系统性地介绍了 java.lang.Module API 的核心能力:

  • ✅ 读取模块元信息(名称、包、类加载器、层)。
  • ✅ 通过 ModuleDescriptor 解析模块的依赖、导出、服务等结构。
  • ✅ 在运行时动态修改模块的可访问性(导出、读取、开放、使用)。

这套 API 为构建灵活的模块化框架和解决模块化环境下的反射、类加载问题提供了强大支持。

所有示例代码均可在 GitHub 上获取:https://github.com/baeldung/tutorials/tree/master/core-java-modules/core-java-9-jigsaw


原始标题:Java 9 java.lang.Module API

» 下一篇: Spring 核心注解