You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 15:54:50