web-dev-qa-db-ja.com

ServiceLoaderを使用してプラグインjarを動的にロードする

アプリケーション用のプラグインシステムを作成しようとしていますが、簡単なものから始めたいと思います。すべてのプラグインは.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_を使用して、2つの選択肢を試しました。

1。クラスパスを動的に拡張する

これは、クラスパスに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の使用

これは、クラスClassLoaderの2番目の引数を持つ_ServiceLoader.load_の別の定義を使用します。

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

繰り返しますが、イテレータには「次の」要素はありません。

クラスパスとロードを「プレイ」するのは初めてなので、きっと何か足りないものがあります。

36
MaxArt

問題は非常に簡単でした。バカ。プラグインの.jarファイルでは、/services/plugintest.SimplePluginファイルがMETA-INFディレクトリ内にないため、ServiceLoaderはjarをサービスとして識別できず、クラスをロードできませんでした。

これでほとんどすべてです。2番目の(よりクリーンな)方法は魅力のように機能します。

37
MaxArt

Java 9から開始すると、スキャンを提供するサービスがはるかに簡単で効率的になります。META-INF/services

インターフェイスモジュール宣言で宣言します。

uses com.foo.spi.Service;

そして、プロバイダーのモジュールで:

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

アプリケーションの概念のソリューションは、Oracleドキュメントですでに説明されています(JARの動的なロードを含む)

Java Platform)を使用した拡張可能なアプリケーションの作成 http://www.Oracle.com/technetwork/ articles/javase/extensible-137159.html

記事の下部にリンクがあります

  • 例のソースコード
  • Javadoc ServiceLoader API

私の意見では、Omer Schleiferが言ったように、車輪の再発明よりもOracleの例を少し修正する方が良いと思います。

1
ANTARA