Service Provider在Jar外运行正常但Jar内报错排查
Kotlin SPI打包Jar后ServiceConfigurationError问题解决
问题场景
Kotlin编写的SPI服务程序,以模块方式运行正常,但打包成Jar后执行java -jar命令抛出ServiceConfigurationError,提示无法获取服务实现类的公共无参构造方法。
核心代码
import java.util.ServiceLoader interface A data object B : A { @JvmStatic fun provider(): A = this } fun main() { println(ServiceLoader.load(A::class.java).toList()) }
运行情况
- 模块方式运行正常:
java.exe -p <program path>;<kotlin stdlib path> -m testJARSPI/my.program.MainKt - Jar方式运行报错:
java -jar <path to jar>
错误栈信息
Exception in thread "main" java.util.ServiceConfigurationError: my.program.A: my.program.B Unable to get public no-arg constructor at java.base/java.util.ServiceLoader.fail(ServiceLoader.java:586) at java.base/java.util.ServiceLoader.getConstructor(ServiceLoader.java:679) at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1240) at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1273) at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1309) at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1393) at kotlin.collections.CollectionsKt___CollectionsKt.toCollection(_Collections.kt:1295) at kotlin.collections.CollectionsKt___CollectionsKt.toMutableList(_Collections.kt:1328) at kotlin.collections.CollectionsKt___CollectionsKt.toList(_Collections.kt:1319) at my.program.MainKt.main(Main.kt:15) at my.program.MainKt.main(Main.kt) Caused by: java.lang.NoSuchMethodException: my.program.B.<init>() at java.base/java.lang.Class.getConstructor0(Class.java:3641) at java.base/java.lang.Class.getConstructor(Class.java:2324) at java.base/java.util.ServiceLoader$1.run(ServiceLoader.java:666) at java.base/java.util.ServiceLoader$1.run(ServiceLoader.java:663) at java.base/java.security.AccessController.doPrivileged(AccessController.java:569) at java.base/java.util.ServiceLoader.getConstructor(ServiceLoader.java:674) ... 9 more
已排查内容
- MANIFEST.MF、services配置文件夹、module-info.java均配置正确
- 构建方式:使用IntelliJ“从模块创建JAR”功能,将内容提取到目标JAR
- 测试JDK版本:open-jdk 22、open-jdk 19、corretto 22
- 尝试将
provider()方法改为open非final修饰,问题未解决
问题根源
Java的ServiceLoader在类路径模式下(Jar运行)默认会尝试通过公共无参构造方法实例化服务实现类,但Kotlin的data object是单例实现,编译后不会生成公共无参构造方法——它的实例是通过静态INSTANCE字段获取的,这与ServiceLoader的默认实例化逻辑冲突。
模块方式运行正常是因为模块路径下的SPI加载逻辑与类路径存在差异,Kotlin在模块模式下的单例处理让ServiceLoader能够兼容,但类路径下的Jar加载严格遵循无参构造的要求。
解决方法
方案1:修改SPI配置文件,指定工厂方法
在META-INF/services/my.program.A配置文件中,将原来的my.program.B改为指定静态工厂方法的形式:
my.program.B#provider
ServiceLoader会识别#后的方法名,调用B类中标记了@JvmStatic的provider()方法获取实例,而非尝试创建新对象。
方案2:改用普通类实现单例(不推荐)
如果不想修改SPI配置,可以将data object改为普通类,提供公共无参构造方法,但这种方式会破坏单例特性(每次ServiceLoader都会创建新实例),示例代码:
interface A data class B : A { companion object { val INSTANCE = B() } }
显然方案1更符合单例需求,是最优解。
验证
修改SPI配置文件后重新打包Jar,执行java -jar命令,ServiceLoader会正确调用B.provider()获取单例实例,不再抛出错误。
内容的提问来源于stack exchange,提问作者LTBS46
相关产品推荐
相关产品推荐

