SPI加载崩溃本质是模块系统拒绝建立服务契约,需检查module-info.java中uses/provides声明合规性、类加载器匹配及清除META-INF/services干扰。

模块化后 SPI 加载崩溃,本质不是“类找不到”,而是模块系统拒绝建立服务契约——关键在 module-info.java 声明是否合规、类加载器是否匹配、传统配置是否干扰。
检查 module-info.java 的 uses / provides 是否完整且正确
ServiceLoader 在 JDK 9+ 不再扫描类路径,只认模块声明:
- 消费者模块(如主应用)必须写
uses com.example.MyService;,不能写成requires或漏掉分号 - 提供者模块(如插件 JAR)必须写
provides com.example.MyService with com.example.impl.MyServiceImpl;,with后只能是具体实现类,不能是抽象类或工厂 - 接口和实现类都必须是
public、非内部类、有public无参构造器 - 提供者模块还需
exports com.example.impl;,否则实现类不可见
确认类加载与模块层可见性未被切断
即使声明正确,运行时仍可能因隔离失败而加载为空:
- 调用
ServiceLoader.load()时,显式传入接口类的类加载器:ServiceLoader.load(MyService.class, MyService.class.getClassLoader())
避免依赖线程上下文类加载器(TCCL),它在 fat-jar 或自定义 Layer 下常失效 - 若使用
ModuleLayer,打印layer.modules()确认插件模块确已链接到该层,而非被父层屏蔽 - 运行时检查:用
MyService.class.getModule().getDescriptor()验证模块是否加载、是否导出对应包
清除 META-INF/services 的干扰并验证内容纯净
模块环境下,META-INF/services/ 文件被完全忽略;但若残留,反而掩盖真实问题:
- 彻底删除插件 JAR 中的
META-INF/services/目录——它在 JPMS 下无效,仅会误导排查 - 用
jar -tf plugin.jar | grep "META-INF/services/"确认打包后无残留 - 如果仍需兼容非模块环境,应通过构建脚本条件化生成该目录,而非手动维护
捕获并逐个诊断 provider,避免静默跳过
默认遍历方式(如 forEachRemaining)遇到第一个失败就终止,不报错也不继续:
- 改用手动迭代 + try-catch:
Iterator<MyService> it = loader.iterator();<br>while (it.hasNext()) {<br> try { MyService s = it.next(); s.doSomething(); }<br> catch (ServiceConfigurationError e) { log.error("SPI load failed for {}", it.next().getClass(), e); }<br>}- 对每个 provider 打印其类加载器:
provider.getClass().getClassLoader(),并与MyService.class.getClassLoader()对比,二者须存在委托关系(如父子)

















