serviceloader 在 jpms 中依赖 module-info.java 的 uses/provides 指令实现模块感知、类型安全的服务发现;需满足提供方 provides、使用方 uses 且 requires 接口模块三条件,否则加载失败。

在 Java 模块化系统(JPMS)中,ServiceLoader 不再简单扫描 META-INF/services 文件,而是与模块声明深度绑定——它依赖 module-info.java 中的 uses 和 provides 指令,在模块图范围内完成服务发现和加载。整个过程是类型安全、模块感知且启动时可验证的。
服务加载的前提:模块间正确声明依赖关系
必须满足三个基础条件,否则 ServiceLoader.load() 会返回空结果或抛出异常:
- 提供方模块(如
my.config.nacos)在module-info.java中用provides X with Y明确注册实现类 - 使用方模块(如
my.app)在module-info.java中用uses X声明“我要消费这个服务接口” - 使用方模块还需
requires服务接口所在的 API 模块(如requires my.config.spi;),但**不需要**requires实现模块
运行时加载服务的标准写法
代码层面与非模块化项目几乎一致,但语义更严格:
- 调用
ServiceLoader.load(ServiceInterface.class)时,JVM 会基于当前线程上下文类加载器(通常是系统类加载器)查找所有已解析模块中满足provides的实现 - 实现类必须是 public、非 abstract、有 public 无参构造器;不能是匿名类、局部类或非 static 内部类
- 若多个模块提供了同一接口,
ServiceLoader按模块解析顺序返回全部实现,可用findFirst()或遍历处理
常见失败原因与排查要点
服务没被加载出来?优先检查以下几点:
- 实现类所在包是否被导出?不必须
exports,但若服务加载需反射实例化,该类必须能被类加载器定位到(即不在 module-private 包内) -
provides声明中的接口全限定名是否与load()参数完全一致(包括包名大小写) - 运行时是否将提供方模块放在了
--module-path上?放在-cp(类路径)下会被归入匿名模块,provides声明无效 - 是否遗漏
uses声明?没有它,JVM 不会在模块图中主动查找该服务的提供者
进阶:动态加载与多版本支持
模块化环境下,可通过 ModuleLayer 构建独立服务层,实现插件热插拔:
- 用
ModuleFinder扫描新模块 JAR,构建新Configuration - 调用
parentLayer.defineModulesWithOneLoader(config, classLoader)创建新层 - 在新层中调用
ServiceLoader.load(..., layer),即可隔离加载该层提供的服务,不影响主层
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











