使用ServiceLoader动态加载插件jar

Max*_*Art 39 java jar classpath serviceloader

我正在尝试为我的应用程序创建一个插件系统,我想从简单的东西开始.每个插件都应该打包在.jar文件中并实现SimplePlugin接口:

package plugintest;

public interface SimplePlugin {
    public String getName();
}
Run Code Online (Sandbox Code Playgroud)

现在我创建了一个SimplePlugin包装在.jar中的实现,并将其放在主应用程序的plugin /子目录中:

package plugintest;

public class PluginTest implements SimplePlugin {
    public String getName() {
        return "I'm the plugin!";
    }
}
Run Code Online (Sandbox Code Playgroud)

在主应用程序中,我想获得一个实例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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

插件/目录按预期添加(因为可以检查调用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());
    }
}
Run Code Online (Sandbox Code Playgroud)

再一次,迭代器永远不会是"下一个"元素.

肯定有一些我不知道的东西,因为这是我第一次"玩"类路径和加载.

Max*_*Art 37

问题很简单.而且很愚蠢.在插件.jar文件中,/services/plugintest.SimplePlugin文件在META-INF目录中丢失,因此ServiceLoader无法将jar标识为服务并加载类.

这几乎就是全部,第二种(更清洁)的方式就像魅力一样.


Mor*_*hai 5

从 Java 9 开始,提供扫描的服务将变得更加容易和高效。不再需要META-INF/services.

在接口模块声明中声明:

uses com.foo.spi.Service;
Run Code Online (Sandbox Code Playgroud)

在提供者的模块中:

provides com.foo.spi.Service with com.bar.ServiceImplementation
Run Code Online (Sandbox Code Playgroud)