使用 ServiceLoader 动态加载插件 jar [英] Dynamically loading plugin jars using ServiceLoader
问题描述
我正在尝试为我的应用程序创建一个插件系统,我想从一些简单的事情开始.每个插件都应该打包在一个 .jar 文件中并实现 SimplePlugin
接口:
I'm trying to create a plugin system for my application, and I want to start with something simple. Every plugin should be packed in a .jar file and implement the SimplePlugin
interface:
package plugintest;
public interface SimplePlugin {
public String getName();
}
现在我已经创建了一个 SimplePlugin
的实现,打包在一个 .jar 中并将它放在主应用程序的 plugin/子目录中:
Now I've created an implementation of SimplePlugin
, packed in a .jar and put it in the plugin/ subdirectory of the main application:
package plugintest;
public class PluginTest implements SimplePlugin {
public String getName() {
return "I'm the plugin!";
}
}
在主应用程序中,我想获得一个 PluginTest
的实例.我尝试了两种替代方法,都使用 java.util.ServiceLoader
.
In the main application, I want to get an instance of PluginTest
. I've tried two alternatives, both using java.util.ServiceLoader
.
1.动态扩展类路径
这使用了已知的hack,在系统类加载器上使用反射来避免封装,以便将URL
添加到类路径中.
This uses the known hack to use reflection on the system class loader to avoid encapsulation, in order to add URL
s the the classpath.
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();
}
}
}
plugins/目录按预期添加(因为可以检查调用 sysLoader.getURLs()
),但是 ServiceLoader
对象给出的迭代器是空的.
The plugins/ directory is added as expected (as one can check calling sysLoader.getURLs()
), but then the iterator given by the ServiceLoader
object is empty.
2.使用 URLClassLoader
这使用了 ServiceLoader.load
的另一个定义和 ClassLoader
类的第二个参数.
This uses another definition of ServiceLoader.load
with a second argument of the class 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());
}
}
再一次,迭代器从来没有下一个"元素.
Once again, the iterator has never a "next" element.
肯定是我遗漏了一些东西,因为这是我第一次玩"类路径和加载.
There's surely something I'm missing since it's the first time I'm "playing" with class paths and loading.
推荐答案
问题很简单.和愚蠢的.在插件 .jar 文件中,/services/plugintest.SimplePlugin
文件在 META-INF
目录中丢失,因此 ServiceLoader
不能将 jar 识别为服务并加载类.
The problem was very simple. And stupid. In the plugin .jar files the /services/plugintest.SimplePlugin
file was missing inside the META-INF
directory, so the ServiceLoader
couldn't identify the jars as services and load the class.
差不多就是这样,第二种(也是更简洁的)方式很有魅力.
That's pretty much all, the second (and cleaner) way works like a charm.
这篇关于使用 ServiceLoader 动态加载插件 jar的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!