Java でプラグイン機構を簡単に実現する

Java プラグイン機構」で検索をすると、ServiceLoader を使っていない記述が多くて
難しそうに見えるので、ServiceLoader を使う方法をまとめておきます。
主にチームメンバーへの情報共有が目的。

  • Java 1.7.0 update51 (Java6 以降でないと ServiceLoader が使えない)
  • Maven 2.2.1
  • Eclipse 4.3.1 Kepler

実現したいこと

  • Javaプラグイン機構を実現したい
  • リフレクションやクラスローダは意識したくない
  • jar ファイルをクラスパスに追加するだけで勝手にプラグインが追加される

プラグインを作ってみる

各国の言葉で挨拶をする機能をプラグインとして作ります。
プロジェクトは foo-core という名前にします。

foo-core/src/main/java/foo/GreetingPlugin.java

package foo;
public interface GreetingPlugin {
    String hello();
}

実装してみます。

foo-core/src/main/java/foo/JapaneseGreetingPlugin.java

package foo;
public class JapaneseGreetingPlugin implements GreetingPlugin {
    public String hello() {
        return "こんにちは";
    }
}

ここまでは、普通のインタフェースとその実装ですね。

次に「プロバイダ構成ファイル」を作成します。

foo-core/src/main/resources/META-INF/services/foo.GreetingPlugin
というパスに以下の内容でファイルを置きます。

foo.JapaneseGreetingPlugin

ファイル名は META-INF/services/[インタフェースの FQN] として
中身にその実装の FQN を列挙します。

以上で、JapaneseGreetingPlugin はプラグイン化されました(!)

今のプロジェクトの構成は以下のようになっています。

プラグインを使ってみる

作ったプラグインを使ってみます。

Main クラスを作ります。

foo-core/src/main/java/foo/Main.java

package foo;

import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;

public class Main {
    private static final GreetingPlugin[] GREETING_PLUGINS = collectAllGreetingPlugins();

    private static GreetingPlugin[] collectAllGreetingPlugins() {
        List<GreetingPlugin> list = new ArrayList<GreetingPlugin>();
        ServiceLoader<GreetingPlugin> loader = ServiceLoader.load(GreetingPlugin.class, Thread.currentThread().getContextClassLoader());
        for (GreetingPlugin plugin: loader) {
            list.add(plugin);
        }
        return (GreetingPlugin[]) list.toArray(new GreetingPlugin[list.size()]);
    }

    public static void main(String[] args) {
        for (GreetingPlugin plugin : GREETING_PLUGINS) {
            System.out.println(plugin.hello());
        }
    }
}

今回はちょっと複雑に見えますが、やっていることは

  1. ServiceLoader.load を使って、GreetingPlugin の実装クラスを集めて、static フィールドに代入しておく
  2. main メソッドの中ですべての実装クラスの hello メソッドを呼び出す

だけです。


このクラスを実行してみます。(jar ファイルは mvn package で作りました)

$ java -cp foo-core-0.0.1-SNAPSHOT.jar foo.Main
こんにちは

「こんにちは」と表示されましたね。

注目する点は、Main の実装の中に JapaneseGreetingPlugin というクラス名が出てこない点です。
プラグインっぽいですね。

プラグインを追加してみる。

いよいよ追加のプラグインを作ってみます。
追加なのでプロジェクトをわけてみます。
英語で挨拶するプラグインプロジェクトなので foo-en としてみました。

事前に foo-core プロジェクトを mvn install したり、
foo-en/pom.xml には foo-core への依存を追加したりしていますが、説明は割愛します。

foo-en/src/main/java/foo/EnglishGreetingPlugin.java

package foo;
public class EnglishGreetingPlugin implements GreetingPlugin {
    public String hello() {
        return "Hello";
    }
}

foo-en/src/main/resources/META-INF/services/foo.GreetingPlugin

foo.EnglishGreetingPlugin

プラグインをクラスパスに追加して実行してみます。

$ java -cp foo-core-0.0.1-SNAPSHOT.jar:foo-en-0.0.1-SNAPSHOT.jar foo.Main
こんにちは
Hello

foo-core の jar を一切変更することなく「Hello」の表示が追加されています。
もちろん同じように GreetingPlugin の実装を追加していけば、
更にプラグインを追加していくことができます。

Web アプリケーションの場合

VirtualWebappLoader を組み合わせて使うと便利です。

まとめ

Javaプラグイン機構を実現するなら ServiceLoader が便利!