Java でプラグイン機構を簡単に実現する
「Java プラグイン機構」で検索をすると、ServiceLoader を使っていない記述が多くて
難しそうに見えるので、ServiceLoader を使う方法をまとめておきます。
主にチームメンバーへの情報共有が目的。
プラグインを作ってみる
各国の言葉で挨拶をする機能をプラグインとして作ります。
プロジェクトは 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()); } } }
今回はちょっと複雑に見えますが、やっていることは
- ServiceLoader.load を使って、GreetingPlugin の実装クラスを集めて、static フィールドに代入しておく
- 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 を組み合わせて使うと便利です。