登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  java教程

Java ModuleDescriptor.Provides 怎样读取服务提供者的类名

来源:17golang原创

时间:2026-11-06 19:57:15 176浏览 收藏

先从 ModuleDescriptor.provides() 取得服务提供声明集合,再对每个 ModuleDescriptor.Provides 调用 service() 和 providers()。前者是服务类型的二进制名称,后者是提供者或提供者工厂的二进制类名列表,返回的是字符串而不是服务实例。

这适合做模块声明清单、部署诊断和报告展示。名单描述“模块声明了什么”,不能据此断言类已经加载、服务已经成功创建或业务调用已经通过。下面先给可复制的最小示例,再说明运行时入口和记录范围。

Java 官方地址:https://dev.java/

最小写法:遍历 provides 声明

相关模块 API 从 Java 9 起提供。为便于单独理解,示例用构建器创建一个内存中的描述符。示例名称是教学数据,不要求真实提供者类存在,也没有在本机执行来证明服务可用。

import java.lang.module.ModuleDescriptor;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class ProvidesNames {
    public static void main(String[] args) {
        // 这里只构造声明元数据,不加载或实例化这些教学类名。
        ModuleDescriptor descriptor = ModuleDescriptor
            .newModule("demo.reports")
            .provides("demo.spi.ReportService",
                List.of("demo.impl.TextReport", "demo.impl.ReportFactory"))
            .build();

        List declarations =
            new ArrayList(descriptor.provides());
        declarations.sort(Comparator.comparing(
            ModuleDescriptor.Provides::service));

        for (ModuleDescriptor.Provides entry : declarations) {
            System.out.println("服务类型:" + entry.service());
            for (String providerName : entry.providers()) {
                System.out.println("声明的提供者或工厂:" + providerName);
            }
        }
    }
}

provides() 是一组服务声明,不能直接当成类名集合;真正的名称列表位于每个声明的 providers() 中。示例按服务名称排序,目的是让报告排列稳定,而不是设置服务选择优先级。

模块描述符、Provides声明与服务和提供者名称字段关系
服务声明结构说明图:模块声明分组关联 Provides,名称字段分组区分 service() 和 providers()。图中连线表示静态结构,不是服务创建流程或运行截图。

给名称列表保留模块与服务上下文

生产报告中只保存一个类名不够:它属于哪个模块,为哪个服务类型声明,也应一起记录。可把一条报告行理解为模块名、服务类型名和提供者名称列表三个字段;不要把多个模块同名服务的声明混成一条没有来源的字符串。

例如上面的 demo.impl.ReportFactory 只是放入描述符的名称。API 文档允许该列表表示提供者或提供者工厂,因此报告字段宜叫“声明的提供者或工厂”,不能无条件标成“接口实现类”。

providers() 的返回列表非空且不可修改。如果需要排序展示,先创建副本再排序,保留原声明列表,避免对不可修改列表调用 sort() 导致异常。

// entry 是上文循环中的一项服务声明,只改展示副本。
List displayedNames = new ArrayList(entry.providers());
displayedNames.sort(String::compareTo);
String moduleName = descriptor.name();
String serviceName = entry.service();
// 写日志时应分别保存这些字段,不把排序理解为加载优先级。
报告行的模块名、服务类型和提供者名称列表组成
声明记录结构说明图:报告字段保留来源上下文,声明名称含义分组提示类名可能对应提供者类或提供者工厂;不表示二者一定同时存在。

只读取元数据,不把类名当作可用实例

把描述符里的名字读出来,与实际发现并创建服务,是两种不同操作。前例只构造元数据,打印名称不会自动检查这些教学类的构造方法或工厂方法。将这段代码用于清单时,不必为了“证明类存在”追加反射实例化。

同样,描述符的 provides 声明不替代模块图、类加载器和实际服务发现上下文。若读者要解决的是服务加载失败,应另行调查发现和实例化阶段;本文的结果只能作为声明依据,不能作为可用性证明。

用于生产清单时,保持工具只读元数据即可,不需要为了读名称额外开放包或修改模块可读关系。不要根据名单自动执行未知构造器,也不要以修改 opens 作为枚举字符串的必要步骤。

从运行时模块取值时处理空描述符

已有某个已知业务类时,可以通过它所属的运行时模块取得描述符。关键是先处理未命名模块:Module.getDescriptor() 在这种情况下返回 null。代码位于 classpath 时,不应把没有描述符误判为“模块声明了零个服务”。

// 放在上文 main 中使用;实际项目可替换为已知业务类。
Module module = ProvidesNames.class.getModule();
ModuleDescriptor runtimeDescriptor = module.getDescriptor();

if (runtimeDescriptor == null) {
    System.out.println("当前类属于未命名模块,没有模块描述符");
} else {
    // 空集合与没有描述符是两种不同状态。
    List entries =
        new ArrayList(runtimeDescriptor.provides());
    entries.sort(Comparator.comparing(ModuleDescriptor.Provides::service));
    for (ModuleDescriptor.Provides entry : entries) {
        System.out.println(runtimeDescriptor.name() + " | "
            + entry.service() + " | " + entry.providers());
    }
}

该片段只观察这个类所属的模块,不扫描所有模块。若读到的集合为空,只能说这个描述符没有提供声明;不能据此推导整个进程没有服务,也不能拿它否定其他模块中的声明。

记录与交付时保留哪些信息

数据或状态准确含义记录建议
descriptor.name()被读取描述符的模块名作为报告来源
entry.service()服务类型的二进制名称与提供者名单一起保存
entry.providers()提供者或工厂的二进制类名列表保留原值,展示排序使用副本
描述符为 null当前运行时模块未命名单独标记,不写作空服务清单
provides 集合为空此描述符没有提供声明保留模块上下文

交付这类诊断结果时,应同时记录从哪个模块和哪份应用构建取得描述符,以及这是“声明清单”。不要写成“服务健康检查全部通过”。类名是二进制名称,记录时保持原字符串,不擅自把其中的美元符号替换为点号,也不改写成文件系统路径。

为什么不能直接修改 providers() 返回的列表?

该 API 返回不可修改列表。需要过滤或排序时新建副本,不把元数据对象当成可变配置。

读取到的名称一定是实现接口的类吗?

不能这样假设。文档明确包含提供者工厂名称;元数据读取本身也不验证实际创建能否成功。

这个方法会实例化服务吗?

不会。本文调用描述符的名称访问方法,只读取声明字段;实际服务发现与创建不在该片段内。

参考官方文档:Provides 名称访问方法、ModuleDescriptor、运行时 Module。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>