问题
Have been trying this since few days and can't get it working!
I am trying to build a pluggable java application where I can run it from command line and provide plugins (jars) in a separated folder. It seems the ServiceLoader
would fit my requirement but I think I need a special case where the jars are not part of the classpath whereas they are stored in a different location, and for this reason I would need to use a ClassLoder
pointing its url to this file system path.
One of the plugin i want to provide to the main application is a log jar with some custom features.
Here below the code I am using , but can't get to go into the for/loop .. it means that the ServiceLoader
is not able to identify/match any class implementation :
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");
}
I wish you could help! Thanks a lot
==== adding project files :
Main pluggable application :
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");
}
}
}
the "Log" plugin
The interface 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);
}
Its implementation
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);
}
}
- Structure
=================
Adjusted the project structure as following but still doesnt work :
Main App :
Extension app :
回答1:
It doesn't work because it is not the same class Log
, your main method try to find implementations of com.company.dep.automation.pluggable.Log
while your jar defines an implementation of com.company.automation.service.Log
such that ServiceLoader.load
simply cannot find anything.
You should move the interface com.company.automation.service.Log
from the extension jar to the project with your Main
class and import com.company.automation.service.Log
instead of com.company.dep.automation.pluggable.Log
in your Main
class then everything should work.
回答2:
Regarding the "pluggable" part of your application and the loading of jars in a folder, instead of re-inventing the wheel, maybe you could have a look at OSGI. By using an OSGI implementation (Apache Felix for example), you could load jars plugins into your application. This is how Eclipse plugins are working for example. (Atlassian is also using this OSGI mechanism for their Jira/Confluence/etc plugins).
OSGI will handle a lot of issue that you will encounter one day if you want a pluggable plugin system.
For example, this is how I'm doing it:
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);
}
}
}
来源:https://stackoverflow.com/questions/40284401/serviceloader-using-classloader-pointing-to-different-path