Jetty中的ServiceLoader问题

时间:2013-12-26 09:09:19

标签: java plugins jetty serviceloader

我正在开发一个应该能够在运行时加载插件的Web应用程序。我知道OSGi对此是一个优雅的解决方案,但是当使用GWT时,我看不到向OSGi的过渡发生。

要实现插件,我有3个jar:应用程序,pluginAPI:

public interface NotificationPlugin {

   public String getName();
}

和plugin.jar

public class Plugin implements NotificationPlugin {
   private final String PLUGIN_NAME = "Plugin";    
   @Override 
   public String getName() {
       return PLUGIN_NAME;
   }
}

plugin.jar和web应用程序依赖于pluginapi.jar。在应用程序中,我使用单独的URLClassloader加载每个jar文件,因此可以单独卸载它们。

    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());
        }  

现在,上面的代码片段就像一个魅力,当且仅当我在应用服务器之外运行代码时。我们使用Jetty在netbeans中调试GWT,并且应用程序部署在Tomcat上(两者都显示相同的行为)。只要我在应用程序服务器中运行代码,代码就会在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
    }

调试显示插件和接口都已加载,在我看来是正确的,否则Class.forName会失败。同样,没有应用程序服务器也不会发生这种情况。 服务信息位于META-INF / services / xx.xx.pluginapi.NotificationPlugin

我的直觉告诉我,错误与应用程序服务器使用ClassLoaders的方式有关,但我和谷歌没有找到任何引用的运气。有人知道如何克服这个问题吗?非常感谢帮助!

2 个答案:

答案 0 :(得分:1)

请参阅https://www.ibm.com/developerworks/java/library/j-dyn0429/,尤其是此指南:

  

使用多个类加载器时,也可能出现其他类型的混淆。图2显示了类接口和相关实现分别由两个独立的类加载器加载时产生的类标识危机的示例。尽管接口和类的名称和二进制实现是相同的,但是来自一个加载器的类的实例不能被识别为从另一个加载器实现接口。

然后,回想一下@ tom写道:

  

在应用程序中,我使用单独的URLClassloader加载每个jar文件,因此可以单独卸载它们。

解决方案 - 尽管可能不容易实现 - 是为接口和实现接口的类使用相同的类加载器。

答案 1 :(得分:0)

当我开始使用war文件以jette启动时,我遇到了问题。这是一个不同ClassLoaders的问题。

当我使用classloader获取getClass().getClassLoader()

时,它会起作用
loader = ServiceLoader.load(Extractor.class, new URLClassLoader(new URL[]{toUrl(pluginPath)}, getClass().getClassLoader()));