Java ServiceLoader не находит загруженный модуль в jar - PullRequest
2 голосов
/ 19 апреля 2020

Я создаю клиент-серверное приложение. Клиент запускает небольшой загрузчик, который загружает клиент в виде jar модуля, но только если файл client.jar изменился. Затем загрузчик пытается запустить клиент через ServiceLoader.

Вот код, который запускает поставщика услуг в клиентском банке.

static PokerGameInstance getPokerGame() {
    URL[] urls = null;

    try {
        urls = new URL[] { Paths.get("client.jar").toUri().toURL() };
        System.out.println(urls[0]);
    }
    catch (Exception e) {
        System.out.println("Could not create URL[] to use to create " +
                "ClassLoader for client.jar.jar.");
        return null;
    }

    URLClassLoader classLoader;
    try {
        classLoader = new URLClassLoader(urls);
    }
    catch (Exception e) {
        System.out.println("Could not create classloader for " +
                "client.jar.");
        return null;
    }

    try { // Test code
        classLoader.loadClass("com.brandli.jbpoker.client.PokerGame");
    }
    catch (ClassNotFoundException e) {
        System.out.println("Could not find PokerGame class");
    }

    ServiceLoader<PokerGameInstance> loader = ServiceLoader
            .load(PokerGameInstance.class, classLoader);
    Optional<PokerGameInstance> optional = loader.findFirst();
    if (optional.isEmpty()) {
        System.out.println("Could not load client service provider.");
        return null;
    }

    return optional.get();
}

При первом запуске нет client.jar. Другой код загружает client.jar, а затем запускается приведенный выше код. Просматривая выходные данные этого метода, URLClassLoader может загрузить класс поставщика услуг (который называется PokerTable). Однако ServiceLoader ничего не находит, и метод выводит «Не удалось загрузить поставщика услуг клиента».

Однако при втором запуске client.jar уже существует, а fre sh - нет. скачал. В этом случае ServiceLoader возвращает правильный класс, и все работает.

Я использую путь к модулю, который включает в себя весь каталог jar-файлов. Там же загружается Client.jar. Итак, во втором запуске система ClassLoader выбирает client.jar. Другими словами, второй проход работает не потому, что ServiceLoader получает client.jar из URLClassLoader. Я проверил это, выполнив второй запуск с параметром ClassLoader для ServiceLoader.load (), для которого установлено значение null.

Я также изменил путь к модулю, включив в него только дискретные файлы jar, чтобы система ClassLoader не принимала клиента .jar, если это там. В этом случае приведенный выше код всегда завершается ошибкой.

В результате ServiceLoader не распознает службу в client.jar, даже если URLClassLoader загрузит объект. Это не имеет ничего общего с загружаемым client.jar, потому что проблема существует, даже если client.jar существует с самого начала (если не обнаружен системой ClassLoader).

Помните, что client.jar является модулем баночка. Код выше находится в модуле, который имеет эту информацию модуля. java:

module com.brandli.jbpoker.loader {
    exports com.brandli.jbpoker.loader;

    requires transitive javafx.controls;
    requires transitive com.brandli.jbpoker.core;
    uses com.brandli.jbpoker.loader.PokerGameInstance;
}

Client.jar имеет эту информацию модуля. java:

    module com.brandli.jbpoker.client {

    requires transitive javafx.controls;
    requires transitive com.brandli.jbpoker.core;
    requires transitive com.brandli.jbpoker.loader;
    requires transitive com.brandli.jbpoker.common;

    provides com.brandli.jbpoker.loader.PokerGameInstance with
    com.brandli.jbpoker.client.PokerGame;
}

I подозреваю, что это как-то связано с модулями. У кого-нибудь есть идеи?

1 Ответ

1 голос
/ 20 апреля 2020

Комментарий к моему вопросу заставил меня заглянуть в ModuleLayer / ModuleFinder. Я заметил, что есть ServiceLoader.load(ModuleLayer, Class). Следующий код работает:

static PokerGameInstance getPokerGame() {
    ModuleFinder finder = ModuleFinder.of(Paths.get("client.jar"),
            Paths.get("common.jar"));
    ModuleLayer parent = ModuleLayer.boot();
    Configuration cf = null;
    try {
        cf = parent.configuration()
                .resolveAndBind(finder, ModuleFinder.of(),
                 Set.of("com.brandli.jbpoker.client"));
    }
    catch (Throwable e) {
        return null;
    }

    ClassLoader cl = ClassLoader.getSystemClassLoader();

    ModuleLayer layer = null;
    try {
        layer = parent.defineModulesWithOneLoader(cf, cl);
    }
    catch (Throwable e) {
        return null;
    }
    ServiceLoader<PokerGameInstance> loader = ServiceLoader
            .load(layer, PokerGameInstance.class);

    Optional<PokerGameInstance> optional = loader.findFirst();
    if (optional.isEmpty()) {
        return null;
    }

    return optional.get();
}

Я не знаю, почему код в моем вопросе не работает.

РЕДАКТИРОВАТЬ: Это объяснение от @Slaw:

Для обеспечения обратной совместимости в JPMS существует концепция безымянного модуля (по одному на ClassLoader). Именно здесь размещается код на пути к классам. Это также, где ваш client.jar заканчивается, когда загружается вашим URLClassLoader, несмотря на то, что он имеет файл информации модуля. Классы в безымянном модуле функционируют так же, как и в пре-модульном мире; чтобы ServiceLoader мог найти провайдера, вам нужен файл конфигурации провайдера в META-INF / services. Директивы using и обеспечивает действие только в именованных модулях, что вы получаете при создании ModuleLayer.

...