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
Я не могу найти, какие изменения происходят внутри файла jar.
Примечание: файл MANIFEST.MF
, папка services
и module-info.java
настроены правильно.
Была построена с использованием IntelliJ: создать JAR из модуля → извлечь в целевой JAR.
протестировано с помощью JDK:
попробовал перейти на неконечный член, но ошибка все та же
data object B : A {
@JvmStatic
@Suppress("NON_FINAL_MEMBER_IN_OBJECT")
open // remove final modifier
fun provider(): A = this
}
Всякий раз, когда в документации ServiceLoader говорится о методах провайдера1, это всегда происходит в контексте модулей. Возможно, это могло бы быть яснее, но подразумевается, что метод поставщика будет использоваться только тогда, когда поставщик услуг найден в неавтоматическом именованном модуле. Если поставщик услуг найден в пути к классу (т. е. безымянный модуль), то метод поставщика, если он есть, будет игнорироваться; будет рассматриваться только конструктор провайдера2.
Когда вы запускаете приложение с помощью:
java.exe -p <program path>;<kotlin stdlib path> -m testJARSPI/my.program.MainK
Ваше приложение загружается из пути к модулю и разрешается как именованный модуль. Таким образом, метод provider()
работает так, как вы ожидаете.
Однако когда вы запускаете приложение с помощью:
java -jar <path to jar>
Ваше приложение загружается из пути к классам в безымянный модуль3, а метод provider()
игнорируется. Итак, он ищет конструктор поставщика, который должен быть общедоступным. Но поскольку ваш поставщик услуг — это Kotlin object
, его конструктор без аргументов является частным. Отсюда и NoSuchMethodException
.
Если вы хотите, чтобы вашим поставщиком услуг был Kotlin object
, то я считаю, что вы вынуждены поместить его в путь к модулю, чтобы он загружался в именованный модуль. Таким образом, вы можете использовать метод поставщика для возврата экземпляра синглтона.
Если вы хотите иметь возможность размещать свой код в пути к классам, не делайте поставщика услуг Kotlin object
. Хотя, если вам все еще нужен синглтон, вы можете ввести некоторую косвенность, сделав сервис «фабрикой», возвращающей синглтон. Или просто загрузите только один экземпляр службы и передайте его по мере необходимости.
1. A "provider method" is a public, static, no-argument method named provider
.
2. A "provider constructor" is a public, no-argument constructor.
3. All code loaded from the class-path is put into the "unnamed module". Any module-info.class
file is ignored in this case (and none of its directives apply).
Я в замешательстве, поскольку
module-info.java
делает его именованным модулем, должен ли метод поставщика не работать?
- comment
Дескриптор информации о модуле игнорируется, когда модуль загружается из пути к классу. Такое поведение является преднамеренным; это помогает немодульному коду продолжать работать на Java 9+. Дескриптор имеет значение только тогда, когда модуль загружается из пути к модулю. Опция -jar
помещает указанный JAR-файл (и все зависимости, перечисленные в атрибуте Class-Path
манифеста) по пути к классам, а не к пути к модулю.
Используйте аргументы -p
/--module-path
, чтобы указать путь к модулю. Обратите внимание, что вам также необходимо убедиться, что модуль разрешен. Это означает, что модуль должен либо быть корневым модулем, либо прямо или косвенно требоваться корневому модулю, либо предоставлять услугу, используемую разрешенным модулем. Корневые модули задаются аргументами --add-modules
и -m
/--module
.
Я в замешательстве, поскольку module-info.java
делает его именованным модулем, должен ли метод поставщика не работать?
изменение параметра (например, с -jar
на -cp
) не может это исправить?
@LTBS46 -cp
— это путь к классу. Вы можете использовать -p
/--module-path
, чтобы указать на модуль, если вы хотите, чтобы он действительно был разрешен как именованный модуль. Точно так же, как вы сделали со своей рабочей командой.
Кроме того, поскольку метод
provider()
не работает с путем к классу, нет смысла предоставлять файл объявления вMETA-INF/services
, поскольку он все равно не будет работать. Для именованного модуля достаточно объявленияprovides
вmodule-info.java
.