当前位置:首页 > 文章列表 > 文章 > java教程 > Java ServiceLoader在模块路径中注册服务实现的实现方法

Java ServiceLoader在模块路径中注册服务实现的实现方法

来源:17golang原创 2026-09-20 06:09:29 0浏览 收藏

Java ServiceLoader 放到模块路径上使用时,关键不是扫描所有 JAR,而是让消费模块和实现模块在 module-info.java 中分别声明服务关系:消费方写 uses com.example.codec.Codec,提供方写 provides com.example.codec.Codec with com.example.codec.internal.TextCodec。服务接口所在包需要被导出,真正的实现包可以保持封装。

官方资料:https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/util/ServiceLoader.html

最小闭环是“API 模块导出服务接口、应用模块 uses、插件模块 provides with”,然后用 ServiceLoader.load(Codec.class) 读取实现。

先把三个模块的边界分清

示例用一个 Codec 服务接口连接三个模块:com.example.codec.api 只放接口,com.example.codec.provider 放具体实现,com.example.codec.app 负责加载。这样应用依赖的是接口,而不是实现类。

// 服务接口模块:只公开稳定的 API 类型
module com.example.codec.api {
    exports com.example.codec;
}

// 消费模块:声明它可能发现 Codec 实现
module com.example.codec.app {
    requires com.example.codec.api;
    uses com.example.codec.Codec;
}

// 提供模块:注册实现,但不必 exports 内部包
module com.example.codec.provider {
    requires com.example.codec.api;
    provides com.example.codec.Codec
        with com.example.codec.internal.TextCodec;
}

这里的 uses 是消费方的服务依赖,不会引入某个具体实现;provides ... with ... 则把服务类型和实现类绑定起来。实现包不写 exports 是有意的:调用方只看到 Codec,实现细节仍由提供模块控制。

Java ServiceLoader 三模块边界说明图:API 服务接口、应用 uses 与提供模块 provides with 的关系
图1:模块边界说明图,展示 API、消费方与提供方之间的 uses/provides 关系,不是运行截图。

实现类要满足 ServiceLoader 的实例化规则

实现类可以实现服务接口,也可以提供一个公开的静态 provider() 工厂方法。为了让示例最容易排查,先采用 public 无参构造器,并把实现放在未导出的内部包中。

// 提供模块内部实现:构造器供 ServiceLoader 调用
package com.example.codec.internal;

import com.example.codec.Codec;

public final class TextCodec implements Codec {
    public TextCodec() {
        // 保留公开无参构造器,避免模块化加载时无法实例化
    }

    @Override
    public String name() {
        return "text";
    }
}

如果改用 provider 方法,方法必须是 public static、无参数,并返回可赋给服务类型的对象;这时工厂类本身不一定实现服务接口。无论采用哪种方式,提供方都必须列在 provideswith 后面,类名必须是全限定名。

应用侧用 ServiceLoader 读取已注册实现

应用模块只引用服务接口。迭代时才会逐个创建实现实例,应用不需要知道提供方的模块名或内部包名。

// 应用入口:只依赖 Codec 服务,不直接依赖 TextCodec
package com.example.codec.app;

import com.example.codec.Codec;
import java.util.ServiceLoader;

public final class Main {
    public static void main(String[] args) {
        ServiceLoader loader = ServiceLoader.load(Codec.class);
        boolean found = false;
        for (Codec codec : loader) {
            found = true;
            System.out.println("发现实现: " + codec.name());
        }
        if (!found) {
            // 空结果说明模块路径或服务声明仍有一处没有接通
            System.err.println("未发现 Codec 提供方");
        }
    }
}

应用模块的 module-info.java 仍然要写 uses,即使代码里已经导入了 ServiceLoader。导入 API 只解决编译期类型依赖,uses 才把服务发现关系写进模块描述符。

ServiceLoader 发现契约说明图:uses 服务类型、模块路径上的提供方和迭代器实例化结果
图2:发现契约说明图,展示 ServiceLoader 从服务类型到提供方实例的静态关系,不是运行证据。

用模块路径启动并按证据排查空结果

假设三个模块的源目录分别为 src/com.example.codec.apisrc/com.example.codec.providersrc/com.example.codec.app,可以把编译输出分开放入 mods

预期会看到 发现实现: text。若没有任何实现,先确认提供模块的输出目录里存在 module-info.class,再检查 provides 的服务类型是否与 uses 完全一致。若出现访问或实例化错误,重点看服务接口包是否 exports、实现类是否 public,以及构造器或 provider 方法是否符合规则。

还要区分“没有提供方”和“服务类型本身不可用”:前者通常是模块路径或 provides 漏配,后者是服务接口所在模块没有被正确读取。生产环境可以在启动检查中记录发现的实现名称,但不要把内部实现包名当成应用配置契约。

常见边界与速查

检查项正确做法典型后果
服务接口包API 模块 exports,消费方 requires编译或解析阶段无法访问服务类型
消费声明应用 module-info 写 uses模块化应用无法按服务关系发现
提供声明provider 写 provides 接口 with 实现迭代器为空或加载失败
实现类public 无参构造器,或 public static provider()ServiceConfigurationError

相关问题

为什么实现包可以不 exports?因为模块系统把服务提供关系单独记录在 provides 中,调用方依赖服务接口,不需要直接访问实现类型。

什么时候还要用 META-INF/services?自动模块或未命名模块仍可用传统 provider-configuration 文件;本文的重点是显式命名模块在模块路径中的 uses/provides 声明。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
LibTV节点式视频编辑适合多人交接吗?用四类交接任务做选型LibTV节点式视频编辑适合多人交接吗?用四类交接任务做选型
上一篇
LibTV节点式视频编辑适合多人交接吗?用四类交接任务做选型
Go testing/fstest发现 FS 实现的路径错误的检查方法
下一篇
Go testing/fstest发现 FS 实现的路径错误的检查方法
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    124次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    196次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    142次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    116次使用
  • CMMLU中文大模型评估基准:功能、使用教程与应用场景解析
    CMMLU
    深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
    104次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码