【问题标题】:Dynamically loading plugin jars using ServiceLoader使用 ServiceLoader 动态加载插件 jar
【发布时间】:2013-04-12 16:37:39
【问题描述】:

我正在尝试为我的应用程序创建一个插件系统,我想从一些简单的东西开始。每个插件都应该打包成一个 .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 在系统类加载器上使用反射来避免封装,以便在类路径中添加URLs。

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 对象给出的迭代器为空。

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());
    }
}

再一次,迭代器从来没有“下一个”元素。

我肯定缺少一些东西,因为这是我第一次“玩”类路径和加载。

【问题讨论】:

  • 为什么不使用一个支持反射的简单库呢?如 apache 常用配置与 beans 支持:commons.apache.org/proper/commons-configuration/userguide/…
  • @omerschleifer 因为,正如我所说,这是我第一次玩这类东西,我想了解它们是如何工作的。其次,欢迎您的建议,但图书馆有一个共同的问题,即它们可以做的比您想做的要多,所以事情往往比必要的复杂。我不知道是不是这样,但只有在我最后一次机会的情况下,我才想解析到外部库。
  • 如果这是出于教育目的,请玩得开心。但就复杂性而言,使用像我发给您的简单库而不是自己发明轮子,反射更容易执行和掌握。毕竟,反射是一个很好探索的领域。祝你好运
  • 我想用这个prinzip从硬盘上的任何地方加载plugin-jar。但由于某种原因,我在 iterator.next 上得到了一个ClassNotFoudException。您知道如何将此主体与外部 jar 一起使用吗?

标签: java jar classpath serviceloader


【解决方案1】:

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

差不多就这些了,第二种(更简洁的)方式就像一个魅力。

【讨论】:

    【解决方案2】:

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

    使用 Java 平台创建可扩展的应用程序 http://www.oracle.com/technetwork/articles/javase/extensible-137159.html

    在文章底部,您将找到指向

    的链接
    • 示例源码
    • Javadoc ServiceLoader API

    在我看来,最好稍微修改 Oracle 的示例,而不是像 Omer Schleifer 所说的那样重新发明轮子。

    【讨论】:

      【解决方案3】:

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

      在接口模块声明中声明:

      uses com.foo.spi.Service;
      

      并且在提供者的模块中:

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

      【讨论】:

      • 所以这解决了在程序指定的路径中查找插件的问题 - 如何?
      • ServiceLoader 不太适合这个,jar 必须已经在类路径或模块路径中。
      • 你可以动态添加罐子。查看stackoverflow.com/questions/60764/…
      • 此外,ServiceLoader 构造函数具有接受 ClassLoaderModuleLayer 的重载,因此您仍然可以使用它进行一些动态操作。
      猜你喜欢
      • 2011-06-07
      • 2012-11-10
      • 2019-02-08
      • 1970-01-01
      • 2014-12-29
      • 2016-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多