Java模块化系统中SPI需显式声明uses和provides,不能仅靠META-INF/services;使用者模块用requires和uses声明依赖,提供者模块用provides...with声明实现并exports包。

Java 模块化系统(JPMS)中,SPI 的自动加载需要显式声明服务依赖和提供关系,不能仅靠传统的 META-INF/services 文件。模块系统默认不扫描类路径或模块路径中的服务配置,必须通过 module-info.java 显式声明 uses(消费者)和 provides ... with(提供者),并确保模块间可读性。
服务使用者模块:声明 uses 并确保可读性
调用方模块需在 module-info.java 中:
- 使用
requires声明对服务接口所在模块的依赖(接口定义模块必须被读取) - 使用
uses com.example.MyService告知 JVM 该模块会通过ServiceLoader.load()查找该服务 - 若服务接口在独立模块中(推荐),该模块本身需导出(
exports)对应包
示例(使用者模块):
module app.module {requires service.api;
uses com.example.MyService;
}
服务提供者模块:声明 provides ... with 并导出实现类
实现方模块需:
立即学习“Java免费学习笔记(深入)”;
- 用
provides com.example.MyService with com.impl.MyServiceImpl声明具体实现类 - 确保
MyServiceImpl所在包被导出(exports),否则ServiceLoader无法访问其实例 - 如果实现类依赖其他模块(如日志、工具类),需通过
requires显式声明
示例(提供者模块):
module impl.module {requires service.api;
exports com.impl;
provides com.example.MyService with com.impl.MyServiceImpl;
}
运行时加载:仍用 ServiceLoader,但依赖模块图解析
代码中加载方式不变,但行为受模块系统约束:
-
ServiceLoader.load(MyService.class)仅查找当前模块可读(readable)且已声明provides的模块 - 若提供者模块未被模块图包含(例如未在启动时指定或未被传递依赖引入),则返回空迭代器
- 可通过
ModuleLayer.boot().modules()或Class.forName(...).getModule()调试模块是否已加载并满足uses/provides
常见陷阱与验证建议
容易出错的地方:
- 忘记在提供者模块中
exports实现类所在包 →ServiceLoader创建实例失败(IllegalAccessException) - 服务接口模块未被使用者
requires→ 编译失败或uses无效 - 多个提供者模块未被同时引入模块路径 → 只加载最先发现的一个(按模块层顺序)
- 仍保留
META-INF/services/文件但未在模块中声明provides→ 在模块化环境下被忽略
验证是否生效:启动时加 --show-module-resolution,或运行时打印 ServiceLoader.load(...).stream().count()。


















