我正在开发一个应该能够在运行时加载插件的 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-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());
}
现在,当且仅当我在应用程序服务器之外运行代码时,上面的代码片段就像一个魅力。我们正在使用 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 的方式有关,但我和谷歌没有找到任何参考资料。有谁知道如何克服这个问题?非常感谢您的帮助!