动态加载使用的ServiceLoader插件罐子(Dynamically loading plugi

2019-08-31 13:24发布

我想我的应用程序创建一个插件系统,我想先从简单的东西。 每个插件都应该被装在一个.jar文件并实现SimplePlugin接口:

package plugintest;

public interface SimplePlugin {
    public String getName();
}

现在,我已经创建了一个实现SimplePlugin ,装在一个.jar并把它在主应用程序的插件/子目录:

package plugintest;

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

在主应用程序,我想获得的实例PluginTest 。 我已经尝试使用两种选择,既java.util.ServiceLoader

1.动态延伸类路径

这使用已知的黑客使用反射对系统类加载器,以避免封装,以添加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();
        }
    }
}

如预期的那样插件/目录中加入(作为一个可以检查主叫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());
    }
}

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

有肯定我丢失的东西,因为它是我第一次“玩”与类路径和负载。

Answer 1:

这个问题很简单。 和愚蠢。 在.jar文件的插件/services/plugintest.SimplePlugin文件缺少内部META-INF目录,这样ServiceLoader无法识别的罐子作为服务和加载类。

这几乎是所有的,第二个(和清洁)的工作方式就像一个魅力。



Answer 2:

Starting from Java 9 the service providing scanning will be much easier and efficient. No more need for META-INF/services.

In the interface module declaration declare:

uses com.foo.spi.Service;

And in the provider's module:

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


Answer 3:

为您的应用概念的解决方案已经在Oracle文档(包括动态加载的JAR文件)进行了说明

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

在文章的底部,你会发现链接

  • 的例子中的源代码
  • 的Javadoc的ServiceLoader API

在我看来最好是稍微修改比重新发明轮子的奥马尔施莱弗尔表示,甲骨文的例子。



文章来源: Dynamically loading plugin jars using ServiceLoader