【问题标题】:ServiceLoader issue in JettyJetty 中的 ServiceLoader 问题
【发布时间】:2013-12-26 09:09:19
【问题描述】:

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

为了实现插件,我有 3 个 jars:应用程序、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-application 依赖于 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());
        }  

现在,当且仅当我在应用程序服务器之外运行代码时,上面的代码 sn-ps 就像一个魅力一样工作。我们正在使用 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 的方式有关,但我和谷歌没有找到任何参考资料。有谁知道如何克服这个问题?非常感谢您的帮助!

【问题讨论】:

  • 在您的问题中包括:您尝试访问 ServiceLoader 的位置/方式(什么类?它在哪里:系统、服务器、webapp 等?它是如何调用的:来自您的代码、来自服务器代码,来自 webapp,来自注释,来自侦听器,来自 ServletContextInitializer 等?),以及带有 META-INF/services/** 的 jar 所在的位置(例如:来自 $CLASSPATH env,来自 -cp,来自服务器,来自 WAR WEB-INF/lib,等等...)
  • 您知道,这是可能的,事实上许多最近的 JSR(如 JSR-356,又名 javax.websocket)都依赖于 ServiceLoader。但是您提供的有关 jar 所在位置以及如何访问代码的详细信息在您的问题中缺失。

标签: java plugins jetty serviceloader


【解决方案1】:

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

使用多个类加载器时也可能出现其他类型的混淆。图 2 显示了一个类标识危机的示例,当一个接口和相关实现分别由两个单独的类加载器加载时,就会导致该危机。即使接口和类的名称和二进制实现相同,一个加载器的类实例也不能被识别为实现另一个加载器的接口。

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

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

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

【讨论】:

    【解决方案2】:

    当我开始使用 war 文件在 jette 中启动时遇到了问题。是ClassLoaders不同的问题。

    当我使用classloader 得到getClass().getClassLoader() 时,它可以工作

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

    【讨论】:

      猜你喜欢
      • 2012-10-20
      • 1970-01-01
      • 2012-03-04
      • 2011-05-30
      • 1970-01-01
      • 2013-12-16
      • 2015-03-08
      • 2010-12-18
      • 1970-01-01
      相关资源
      最近更新 更多