ServiceLoader, использующий ClassLoader, указывающий на другой путь
Пытаюсь это уже несколько дней и не могу заставить его работать!
Я пытаюсь создать подключаемое Java-приложение, где я могу запустить его из командной строки и предоставить плагины (jars) в отдельной папке. Кажется ServiceLoader
будет соответствовать моему требованию, но я думаю, что мне нужен особый случай, когда банки не являются частью classpath, тогда как они хранятся в другом месте, и по этой причине мне нужно будет использовать ClassLoder
указав его URL к этому пути файловой системы.
Один из плагинов, который я хочу предоставить основному приложению, - это журнал с некоторыми пользовательскими функциями.
Ниже приведен код, который я использую, но не могу войти в цикл for/... это означает, что 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);
}
}
- Состав
=================
Скорректирована структура проекта следующим образом, но все еще не работает:
2 ответа
Это не работает, потому что это не тот же класс Log
твой основной метод попробуй найти реализации com.company.dep.automation.pluggable.Log
в то время как ваш кувшин определяет реализацию com.company.automation.service.Log
такой, что ServiceLoader.load
просто не могу ничего найти.
Вы должны переместить интерфейс com.company.automation.service.Log
от банки расширения до проекта с вашим Main
класс и импорт com.company.automation.service.Log
вместо com.company.dep.automation.pluggable.Log
в вашем Main
класс тогда все должно работать.
Что касается "подключаемой" части вашего приложения и загрузки jar-файлов в папку, вместо того, чтобы заново изобретать колесо, возможно, вы могли бы взглянуть на OSGI. Используя реализацию OSGI (например, Apache Felix), вы можете загрузить плагины jars в свое приложение. Вот как плагины 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);
}
}
}