我正在尝试为我的应用程序创建一个插件系统,我想从简单的东西开始。每个插件都应该打包在.jar文件中并实现SimplePlugin
接口:
package plugintest;
public interface SimplePlugin {
public String getName();
}
现在我已经创建了SimplePlugin
的实现,打包在.jar中并将其放在主应用程序的plugin /子目录中:
package plugintest;
public class PluginTest implements SimplePlugin {
public String getName() {
return "I'm the plugin!";
}
}
在主应用程序中,我想获得PluginTest
的实例。我尝试了两种方法,都使用java.util.ServiceLoader
。
1。动态扩展类路径
这使用已知的hack在系统类加载器上使用反射来避免封装,以便在类路径中添加URL
。
package plugintest.system;
import plugintest.SimplePlugin;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Iterator;
import java.util.ServiceLoader;
public class ManagePlugins {
public static void main(String[] args) throws IOException {
File loc = new File("plugins");
extendClasspath(loc);
ServiceLoader<SimplePlugin> sl = ServiceLoader.load(SimplePlugin.class);
Iterator<SimplePlugin> apit = sl.iterator();
while (apit.hasNext())
System.out.println(apit.next().getName());
}
private static void extendClasspath(File dir) throws IOException {
URLClassLoader sysLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
URL urls[] = sysLoader.getURLs(), udir = dir.toURI().toURL();
String udirs = udir.toString();
for (int i = 0; i < urls.length; i++)
if (urls[i].toString().equalsIgnoreCase(udirs)) return;
Class<URLClassLoader> sysClass = URLClassLoader.class;
try {
Method method = sysClass.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(sysLoader, new Object[] {udir});
} catch (Throwable t) {
t.printStackTrace();
}
}
}
插件/目录按预期添加(因为可以检查调用sysLoader.getURLs()
),但是ServiceLoader
对象给出的迭代器是空的。
2。使用URLClassLoader
这使用ServiceLoader.load
的另一个定义和类ClassLoader
的第二个参数。
package plugintest.system;
import plugintest.SimplePlugin;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Iterator;
import java.util.ServiceLoader;
public class ManagePlugins {
public static void main(String[] args) throws IOException {
File loc = new File("plugins");
File[] flist = loc.listFiles(new FileFilter() {
public boolean accept(File file) {return file.getPath().toLowerCase().endsWith(".jar");}
});
URL[] urls = new URL[flist.length];
for (int i = 0; i < flist.length; i++)
urls[i] = flist[i].toURI().toURL();
URLClassLoader ucl = new URLClassLoader(urls);
ServiceLoader<SimplePlugin> sl = ServiceLoader.load(SimplePlugin.class, ucl);
Iterator<SimplePlugin> apit = sl.iterator();
while (apit.hasNext())
System.out.println(apit.next().getName());
}
}
再次,迭代器从来没有“下一个”元素。
肯定有一些我不知道的东西,因为这是我第一次“玩”类路径和加载。
答案 0 :(得分:36)
问题很简单。而且很愚蠢。在插件.jar文件中/services/plugintest.SimplePlugin
目录中缺少META-INF
文件,因此ServiceLoader
无法将jar标识为服务并加载该类。
这就是全部,第二种(更清洁)的方式就像魅力一样。
答案 1 :(得分:3)
从Java 9开始,提供扫描的服务将变得更加容易和高效。不再需要META-INF/services
。
在接口模块声明中声明:
uses com.foo.spi.Service;
在提供商的模块中:
provides com.foo.spi.Service with com.bar.ServiceImplementation
答案 2 :(得分:0)
您的应用程序概念的解决方案已在Oracle文档中描述(包括动态加载JAR)
使用Java平台创建可扩展的应用程序 http://www.oracle.com/technetwork/articles/javase/extensible-137159.html
在文章的底部,您将找到
的链接在我看来,最好稍微修改一下Oracle的例子,而不是像Omer Schleifer那样重新发明轮子。