I am working on a web application which should be able to load plugins during runtime. I am aware that OSGi would be a elegant solution for this, but as GWT is used , I do not see the transition to OSGi happening.
To implement the plugin, I have 3 jars: The application, the pluginAPI :
public interface NotificationPlugin {
public String getName();
}
and the plugin.jar
public class Plugin implements NotificationPlugin {
private final String PLUGIN_NAME = "Plugin";
@Override
public String getName() {
return PLUGIN_NAME;
}
}
The plugin.jar and the web-application have dependencies to the pluginapi.jar. In the application I load each jar file with a separate URLClassloader, so they can be unloaded separately.
service.setUcl(new URLClassLoader(url));
ServiceLoader<NotificationPlugin> sl = ServiceLoader.load(NotificationPlugin.class, service.getUcl());
Iterator<NotificationPlugin> apit = sl.iterator();
service.setPlugin(apit.next());
while (apit.hasNext()) {
System.out.println(apit.next().getClass().getName());
}
Now the code snippets above work like a charm, if and only if I run the code outside of an application server. We are using Jetty to debug GWT in netbeans and the application is deployed on Tomcat (both show same behavior). As soon as I run the code in the application server, the code fails at this point in ServiceLoader.java :
public S next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
---> if (!service.isAssignableFrom(c)) { <-----
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated: " + x,
x);
}
throw new Error(); // This cannot happen
}
Debugging shows, that both the plugin and the interface are loaded, properly in my opinion, otherwise Class.forName would fail. Again, this does not happen without an application server. The service information is at META-INF/services/xx.xx.pluginapi.NotificationPlugin
My gut tells me, the error has something to do with the way application servers use ClassLoaders, but me and google had no luck finding any references. Does anybody know how to overcome this issue? Help is kindly appreciated!
See https://www.ibm.com/developerworks/java/library/j-dyn0429/ especially this guidance:
Then, recall that @tom wrote:
The solution -- although it may not be easy to accomplish -- is to use the same classloader for both the interface and the class that implements the interface.