ServiceLoader issue in Jetty

1.2k views Asked by At

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!

2

There are 2 answers

0
Lonnie On

See https://www.ibm.com/developerworks/java/library/j-dyn0429/ especially this guidance:

Other types of confusion are also possible when using multiple class loaders. Figure 2 shows an example of a class identity crisis that results when an interface and associated implementation are each loaded by two separate class loaders. Even though the names and binary implementations of the interfaces and classes are the same, an instance of the class from one loader cannot be recognized as implementing the interface from the other loader.

Then, recall that @tom wrote:

In the application I load each jar file with a separate URLClassloader, so they can be unloaded separately.

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.

0
birdy On

I had the problem when I started using the war file to start in jette. It was a problem of different ClassLoaders.

It works when I use the classloader getting by getClass().getClassLoader()

loader = ServiceLoader.load(Extractor.class, new URLClassLoader(new URL[]{toUrl(pluginPath)}, getClass().getClassLoader()));