Поставщик услуг работает вне файла jar, но не внутри файла jar

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:

  • открытый JDK 22
  • open-jdk 19 (показана трассировка стека)
  • корретто 22

попробовал перейти на неконечный член, но ошибка все та же

data object B : A {
    @JvmStatic
    @Suppress("NON_FINAL_MEMBER_IN_OBJECT")
    open // remove final modifier
    fun provider(): A = this
}
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Всякий раз, когда в документации 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.

Кроме того, поскольку метод provider() не работает с путем к классу, нет смысла предоставлять файл объявления в META-INF/services, поскольку он все равно не будет работать. Для именованного модуля достаточно объявления provides в module-info.java.

Holger 23.07.2024 10:12

Я в замешательстве, поскольку module-info.java делает его именованным модулем, должен ли метод поставщика не работать?

LTBS46 23.07.2024 13:02

изменение параметра (например, с -jar на -cp) не может это исправить?

LTBS46 23.07.2024 13:12

@LTBS46 -cp — это путь к классу. Вы можете использовать -p/--module-path, чтобы указать на модуль, если вы хотите, чтобы он действительно был разрешен как именованный модуль. Точно так же, как вы сделали со своей рабочей командой.

Slaw 23.07.2024 13:14

Другие вопросы по теме