模块化环境下 service 实现类找不到,核心是检查 module-info.java 中 uses 和 provides 声明是否完整:消费者需声明 uses 接口,提供者需声明 provides 具体实现类,且接口与实现类均须 public、可导出、有无参构造器,并确保模块层可见性及清除旧式 meta-inf/services 干扰。

排查模块化环境下找不到 Service 实现类,核心是确认服务契约是否被模块系统识别——不是“类是否存在”,而是“模块是否声明了服务关系”。Java 9+ 模块系统下,ServiceLoader 不再依赖类路径扫描,而是严格校验 module-info.java 中的 uses 和 provides 声明。
检查服务消费者模块是否声明 uses
加载服务的模块(如主应用或插件管理器)必须在 module-info.java 中明确声明它要使用的服务接口:
- 写法必须是
uses com.example.MyService;,不能写成requires或漏掉分号 - 接口类必须已导出(即提供该接口的模块需有
exports com.example;) - 如果用的是
ServiceLoader.load(ModuleLayer, Class),但没声明uses,会直接抛ServiceConfigurationError: module does not declare uses
验证服务提供者模块是否正确 provides
每个插件 JAR 对应的模块,必须在自己的 module-info.java 中声明具体实现:
- 格式为
provides com.example.MyService with com.example.impl.MyServiceImpl; -
with后必须是**具体实现类**,不能是工厂类或抽象类(除非该类本身实现了接口) - 该实现类必须是
public、非内部类、有 public 无参构造器;否则ServiceLoader.Provider::get()会失败 - 确保该类所在的包已被
exports,否则其他模块无法访问
确认类加载与模块层可见性
即使模块声明正确,运行时仍可能因类加载隔离导致加载失败:
- 调用
ServiceLoader.load()时,显式传入目标接口的类加载器:ServiceLoader.load(MyService.class, MyService.class.getClassLoader()),避免默认使用线程上下文类加载器(TCCL)失效 - 若使用自定义
ModuleLayer,确保服务提供者模块已正确链接到该层,且未被父层屏蔽 - 可通过
ModuleLayer.boot().modules()或调试输出layer.modules()查看实际加载的模块列表,确认插件模块确实在其中
排除 SPI 配置层面的干扰
当模块系统启用时,传统 META-INF/services/ 文件会被忽略——但若同时存在模块声明和旧式配置,可能引发冲突或掩盖真实问题:
- 清理插件 JAR 中的
META-INF/services/目录(模块环境下它无效,反而易误导排查) - 确保没有在非模块化方式下尝试加载(例如误用
ServiceLoader.load(MyService.class)而非ServiceLoader.load(layer, MyService.class)) - 编译时用
javac --module-path和--add-modules正确组织依赖,避免模块图断裂










