java 9模块系统中spi需三模块分离:接口模块仅exports接口包,提供模块requires并provides实现类,使用模块requires并uses接口;缺一声明则serviceloader加载失败。

Java 9 引入模块系统(JPMS)后,SPI(Service Provider Interface)机制被深度集成进模块声明中。接口本身不“导出”实现,而是由服务提供方模块通过 provides ... with 声明其具体实现类,供服务使用者模块通过 uses 声明依赖并动态加载。关键在于:接口定义模块、服务提供模块、服务使用模块三者职责分离,且必须在 module-info.java 中显式声明。
接口定义模块需导出包(但不声明 provides)
定义 SPI 接口的模块只需将含接口的包导出(exports),无需也不应写 provides —— 因为接口本身没有实现。例如:
// module-info.java(接口模块:com.example.spi)
module com.example.spi {
exports com.example.spi;
}
其中 com.example.spi.ServiceInterface 是一个 public interface。该模块只负责契约,不参与实现或绑定。
服务提供模块用 provides ... with 绑定实现类
实现该接口的模块必须:
- 声明对接口模块的依赖(
requires) - 在
module-info.java中用provides 接口类型 with 实现类显式注册服务 - 确保实现类是 public 且具有无参构造器(否则 ServiceLoader 加载失败)
示例:
// module-info.java(实现模块:com.example.provider)
module com.example.provider {
requires com.example.spi;
provides com.example.spi.ServiceInterface
with com.example.provider.ServiceImpl;
}
注意:ServiceImpl 必须在该模块可访问的包中,且类名拼写、大小写必须完全匹配。
Java开发手册规约集合,基于阿里巴巴Java开发手册(嵩山版)。 涵盖7大维度:编程规约、异常日志、单元测试、安全规约、MySQL数据库、工程结构、设计规约。 当用户需要:(1) 编写或审查Java代码 (2) 检查命名/代码规范 (3) 处理异常和日志 (4) 编写单元测试 (5) 安全编码 (6) 数据库设...
服务使用模块通过 uses 声明依赖并用 ServiceLoader 加载
调用方模块需:
- 声明依赖接口模块(
requires com.example.spi) - 用
uses 接口类型告知 JVM:“我需要通过 ServiceLoader 查找该接口的实现” - 运行时调用
ServiceLoader.load(接口.class)获取实例
示例:
// module-info.java(使用模块:com.example.app)
module com.example.app {
requires com.example.spi;
uses com.example.spi.ServiceInterface;
}
Java 运行时会扫描所有已解析模块中对该接口的 provides 声明,并加载对应实现。
常见问题与细节提醒
以下情况会导致 ServiceLoader 找不到实现:
- 服务提供模块未在启动时被模块路径(--module-path)包含,或未被显式启用(如未传给
--add-modules) -
provides中的接口类型或实现类名有拼写错误,或实现类不是 public 或缺少无参构造器 - 服务使用模块遗漏
uses声明 —— 这会导致 JPMS 在链接阶段忽略该服务绑定,即使代码调用ServiceLoader.load()也返回空迭代器 - 接口模块未
exports接口所在包,导致其他模块无法编译引用该接口
模块系统不会自动发现服务;uses 和 provides 是强制的、静态的契约声明,而非运行时反射探测。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










