Java 使用ServiceLoader动态加载插件JAR

Java 使用ServiceLoader动态加载插件JAR,java,jar,classpath,serviceloader,Java,Jar,Classpath,Serviceloader,我正在尝试为我的应用程序创建一个插件系统,我想从一些简单的东西开始。每个插件都应该打包在一个.jar文件中,并实现SimplePlugin接口: package plugintest; public interface SimplePlugin { public String getName(); } 现在,我已经创建了一个SimplePlugin的实现,打包在一个.jar中,并将其放在主应用程序的plugin/子目录中: package plugintest; public cl

我正在尝试为我的应用程序创建一个插件系统,我想从一些简单的东西开始。每个插件都应该打包在一个.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!";
    }
}
在主应用程序中,我想获得一个
pluginest
的实例。我尝试了两种选择,都使用
java.util.ServiceLoader

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());
    }
}
1。动态扩展类路径

这使用已知的hack在系统类加载器上使用反射来避免封装,以便在类路径中添加
URL
s

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();
        }
    }
}
同样,迭代器从来没有“下一个”元素


因为这是我第一次“玩”类路径和加载,所以我肯定遗漏了一些东西。

问题很简单。而且很愚蠢。在plugin.jar文件中,
META-INF
目录中缺少
/services/pluginest.SimplePlugin
文件,因此
ServiceLoader
无法将jar识别为服务并加载类


基本上就是这样,第二种(也是更干净的)方法很有魅力。

您的应用程序概念的解决方案已经在Oracle文档中描述过(包括动态加载JAR)

使用Java平台创建可扩展应用程序

在文章的底部,你可以找到到

  • 示例的源代码
  • Javadoc ServiceLoader API

在我看来,稍微修改一下Oracle的示例比重新发明Omer Schleifer所说的轮子要好。

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

在接口模块声明中声明:

uses com.foo.spi.Service;
在提供者的模块中:

provides com.foo.spi.Service with com.bar.ServiceImplementation

为什么不使用支持反射的简单库呢?比如apache common configuration with beans support:@omerschleifer,因为正如我所说,这是我第一次玩这类东西,我想了解它们是如何工作的。第二,你的建议是受欢迎的,但图书馆有一个共同的问题,他们可以做的比你想做的更多,所以事情往往比必要的更复杂。我不知道是不是这样,但我想只有在这是我最后一次机会的情况下,我才去外部图书馆。如果这是为了教育目的,那就玩吧。但就复杂性而言,使用一个简单的库(如我发给您的库)比自己发明轮子更容易执行和掌握反射。毕竟,反射是一个经过充分探索的领域。good luckI想使用这个原则从硬盘上的任何地方加载插件jar。但是由于某种原因,我在迭代器.next上得到了一个
ClassNotFoudException
。你知道如何在外部jar中使用这个原则吗?这样就解决了在程序指定路径中查找插件的问题-how?
ServiceLoader
并不能很好地适应这个问题,jar必须已经在类路径或模块路径中。不过,你可以动态添加jar。还要检查一下,
ServiceLoader
构造函数具有接受
ClassLoader
ModuleLayer
的重载,因此您仍然可以对其进行一些动态操作。