ServiceLoader, использующий ClassLoader, указывающий на другой путь

Пытаюсь это сделать уже несколько дней и не могу заставить его работать!

Я пытаюсь создать подключаемое Java-приложение, в котором я могу запустить его из командной строки и предоставить плагины (jars) в отдельной папке. Кажется, что ServiceLoader соответствует моему требованию, но я думаю, что мне нужен особый случай, когда банки не являются частью пути к классам, тогда как они хранятся в другом месте, и по этой причине мне нужно будет использовать ClassLoder, указывающий его URL-адрес на этот путь к файловой системе.

Один из плагинов, который я хочу предоставить основному приложению, — это log jar с некоторыми пользовательскими функциями.

Ниже приведен код, который я использую, но не могу войти в цикл for/loop. Это означает, что ServiceLoader не может идентифицировать/сопоставить какую-либо реализацию класса:

final URL u = new File("C:\\data\\myLogJar-1.0-SNAPSHOT.jar").toURI().toURL();
ClassLoader ucl = new URLClassLoader(new URL[] {u});

ServiceLoader<Log> loader = ServiceLoader.load(Log.class, ucl);
for (Iterator<Log> iterator = loader.iterator(); iterator.hasNext(); ) {
    System.out.println(iterator.next());
}
loader = ServiceLoader.load(Log.class,ucl);
for (final Log log : loader) {
    log.info("Test log");                    
}

Я хочу, чтобы вы могли помочь! Большое спасибо

==== добавление файлов проекта:

Основное подключаемое приложение:

введите описание изображения здесь

    package com.company.dep.automation;

import com.company.dep.automation.pluggable.Log;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

import java.util.*;

public class Main {

    private static ServiceLoader<Log> serviceLoader;

    public static void main(String[] args) {

        final URL u;
        ClassLoader ucl = null;

        try {
            u = new File("C:\\data\\myLogJar-1.0-SNAPSHOT.jar").toURI().toURL();
             ucl = new URLClassLoader(new URL[]{u});
        } catch (MalformedURLException e1) {
            e1.printStackTrace();
        }

        ServiceLoader<Log> loader = ServiceLoader.load(Log.class, ucl);
        for (Iterator<Log> iterator = loader.iterator(); iterator.hasNext(); ) {
            System.out.println(iterator.next());
        }

        loader = ServiceLoader.load(Log.class, ucl);
        for (final Log log : loader) {
            log.info("Test log");
        }

    }

}

плагин "Журнал"

Интерфейс Log

package com.company.automation.service;

public interface Log {

    void trace(String message);
    void debug(String message);
    void info(String message);
    void warn(String message);
    void severe(String message);
    void error(String message);
    void fatal(String message);

}

Его реализация

package com.company.automation.service.impl;

import com.company.automation.service.Log;

public class LogImpl implements Log {

    @Override
    public void trace(String message) {
        log("TRACE --> " + message);
    }

    @Override
    public void debug(String message) {
        log("DEBUG --> " + message);
    }

    @Override
    public void info(String message) {
        log("INFO --> " + message);
    }

    @Override
    public void warn(String message) {
        log("WARN --> " + message);
    }

    @Override
    public void severe(String message) {
        log("SEVERE --> " + message);
    }

    @Override
    public void error(String message) {
        log("ERROR --> " + message);
    }

    @Override
    public void fatal(String message) {
        log("FATAL --> " + message);
    }

    private void log(String message) {
        System.out.println(message);
    }

}
  • Структура

введите описание изображения здесь

=================

Скорректировал структуру проекта следующим образом, но все еще не работает:

Основное приложение: введите здесь описание изображения

Приложение расширения: введите здесь описание изображения


person user1611183    schedule 27.10.2016    source источник
comment
пожалуйста, покажите структуру вашего файла jar   -  person Nicolas Filotto    schedule 27.10.2016
comment
Извиняюсь! добавление дополнительной информации...   -  person user1611183    schedule 27.10.2016
comment
ваш первый цикл будет зацикливаться навсегда, если у вас есть хотя бы один результат, поскольку вы не вызываете next()   -  person Nicolas Filotto    schedule 27.10.2016
comment
да .. я сделал так много попыток, которые могли оставить ошибку - второй блок все равно не зацикливается .. спасибо, что указали на это   -  person user1611183    schedule 27.10.2016


Ответы (2)


Это не работает, потому что это не тот же класс Log, ваш основной метод пытается найти реализации com.company.dep.automation.pluggable.Log, в то время как ваша банка определяет реализацию com.company.automation.service.Log, так что ServiceLoader.load просто не может ничего найти.

Вы должны переместить интерфейс com.company.automation.service.Log из jar-файла расширения в проект с вашим классом Main и импортировать com.company.automation.service.Log вместо com.company.dep.automation.pluggable.Log в свой класс Main, тогда все должно работать.

person Nicolas Filotto    schedule 27.10.2016
comment
Я только что изменил путь в обоих проектах и ​​все еще не работает. см. мое редактирование выше. Спасибо, Николас! - person user1611183; 27.10.2016
comment
удалите com.company.automation.pluggable.Log из своего приложения-расширения, покажите содержимое вашего файла META-INF/services/com.company.automation.pluggable.Log - person Nicolas Filotto; 27.10.2016
comment
удаление файла com.company.automation.pluggable.Log из проекта расширения вызывает проблему компиляции, поскольку класс LogImpl реализует Log. это содержимое файла служб: com.company.automation.pluggable.impl.LogImpl - person user1611183; 27.10.2016
comment
move Войдите в новый проект и сделайте так, чтобы оба проекта зависели от нового, или просто сделайте так, чтобы ваше приложение-расширение зависело от основного приложения (самое уродливое и простое решение). Идея состоит в том, чтобы предотвратить удвоение класса Log в пути к классам, иначе вы получите странные ошибки, связанные с конфликтом. Действительно, имейте в виду, что даже 2 совершенно одинаковых класса, загруженные из 2 разных CL, считаются разными. - person Nicolas Filotto; 27.10.2016
comment
Я не полностью убежден, хотя это хороший совет. Я хочу изолировать основное приложение от расширений плагина. Мы хотим, чтобы люди здесь, в компании, писали плагины и не имели зависимостей от основного проекта. Затем мы просто хотим поместить jar в папку в файловой системе и позволить основному приложению загрузить его. Возможно ли иметь общий интерфейс (назовите его Plugin), а затем все расширения реализуют интерфейс Plugin? Я гуглил и не нашел ни одного примера, и это немного странно, учитывая пользу, которую ClassLoader может принести ServiceLoader. - person user1611183; 27.10.2016
comment
именно поэтому я предложил первое решение, Log не имеет ничего общего с вашим приложением-расширением, оно должно быть в совершенно другом проекте (например, spi или api). Это был бы настоящий беспорядок, если бы каждый разработчик должен был добавить интерфейс Log в свое приложение-расширение. Пожалуйста, покажите, что у вас есть в META-INF/services/com.company.automation.pluggable.Log - person Nicolas Filotto; 27.10.2016
comment
com.company.automation.pluggable.impl.LogImpl - person user1611183; 27.10.2016
comment
Давайте продолжим обсуждение в чате. - person user1611183; 27.10.2016
comment
Ваше предложение сработало с первой попытки. Вы действительно были правы, предложив создать три отдельных проекта: основной исполнитель, расширение плагина и, конечно же, API, который работает как мост между двумя последними. Спасибо! - person user1611183; 30.10.2016

Что касается «подключаемой» части вашего приложения и загрузки банок в папку, вместо того, чтобы заново изобретать колесо, возможно, вы могли бы взглянуть на OSGI. Используя реализацию OSGI (например, Apache Felix), вы можете загружать подключаемые модули jar в ваше приложение. Например, так работают плагины Eclipse. (Atlassian также использует этот механизм OSGI для своих плагинов Jira/Confluence/etc).

OSGI справится со многими проблемами, с которыми вы однажды столкнетесь, если вам нужна подключаемая система плагинов.

Например, вот как я это делаю:

public ChannelListener init() {
    private ChannelListener listener = new ChannelListener();
    logger.info("Initializing Felix...");
    felix = new Felix(getConfig());

    try {
        felix.start();
    } catch (BundleException e) {
        logger.error("Error while initializing Felix", e);
        throw new RuntimeException(e);
    }

    try {
        // On the next line, you are registering a listener that will listen for all 
        //Log implementations service registration.
        felix.getBundleContext().addServiceListener(listener, "(objectClass=com.company.automation.service.Log)");
    } catch (InvalidSyntaxException e) {
        logger.error("Error while registering service listener", e);
    }
    listener.start(felix.getBundleContext());

    startAllBundlesInDirectory(BUNDLE_DIRECTORY, felix.getBundleContext());
    return listener;
}

private void startAllBundlesInDirectory(String directory, BundleContext context) {
    File bundleFolder = new File(directory);
    File[] bundleFilesList = bundleFolder.listFiles((dir, name) -> name.endsWith(".jar"));

    List<Bundle> installedBundles = new ArrayList<>();
    logger.info("Installing {} bundles in {}.", bundleFilesList.length, directory);
    for(File bundleFile : bundleFilesList) {
        logger.info("Installing {}", bundleFile.getName());
        try {
            installedBundles.add(context.installBundle("file:" + directory + bundleFile.getName()));
        } catch (BundleException e) {
            logger.error("Error while installing bundle {}{}", directory, bundleFile.getName(), e);
        }
    }

    for(Bundle bundle : installedBundles) {
        try {
            bundle.start();
        } catch (BundleException e) {
            logger.error("Error while starting bundle {}{}", directory, bundle.getSymbolicName(), e);
        }
    }
}
person jchampemont    schedule 27.10.2016
comment
это выглядит интересно, и нужно немного поиграть с ним ... единственная проблема в том, что на данный момент есть только один jar, но их должно быть намного больше, поэтому имя файла jar должно исходить из списка jar в папках ... нужно чтобы понять ваш код и попытаться немного настроить мой.. - person user1611183; 27.10.2016
comment
По сути, эта строка File[] bundleFilesList = bundleFolder.listFiles((dir, name) -> name.endsWith(".jar")); читает все jar-файлы папки. - person jchampemont; 27.10.2016