先週のAttach APIは,どちらかというと裏方で使うAPIでした。今週解説するServiceLoaderクラスも,縁の下の力持ち的なクラスです。

第6回でjconsoleのプラグインを作成したときに,META-INFディレクリと,META-INFディレクトリの下にservicesディレクトリを作成したことを覚えているでしょうか。

なんで,こんなことするのだろうと不思議に思った方も多いかもしれません。これがjava.util.ServiceLoaderクラスを使ったコンポーネントのロードなのです。

コンポーネントをロードする

さっそく,サンプルを作って,確かめてみましょう。

サンプルはこちらからダウンロードできます:HowdyTalky.javaHowdyTalkyLoader.javaHowdyTalky_ja.javaHowdyTalky_en.java

はじめに,ロードするコンポーネントを作成します。ありきたりですが,「Hello, World!」を戻すコンポーネントを作ってみることにしましょう。

まずはコンポーネントのファサードになるインタフェースです。インタフェース名はHowdyTalkyとしました。

public interface HowdyTalky {
    public String sayHello();
}

これを実装するクラスを二つ作ってみます。英語版と日本語版です。はじめに英語版。

public class HowdyTalky_en implements HowdyTalky {
    public String sayHello() {
        return "Hello, World!";
    }
}

次に日本語版です。

public class HowdyTalky_ja implements HowdyTalky {
    public String sayHello() {
        return "こんにちは,世界";
    }
}

他愛もないプログラムなので,解説するまでもないですね。

これらをJARファイルにまとめましょう。その前に,META-INF/servicesディレクトリに一つのファイルを作成する必要があります。インタフェースの名前と同一のファイル,つまりここではHowdyTalkyというファイルです。HowdyTalkyファイルの中には使用する実装クラスを記述します。

ここではHowdyTalky_enとHowdyTalky_jpのどちらかになりますが,はじめは英語にしておきましょう。

HowdyTalky_en

このファイルもふくめてJARファイルを作成します。

C:\temp>jar cvf talky.jar META-INF\services\HowdyTalky HowdyTalky*.class

これでコンポーネントはできました。

続いて,このコンポーネントを使うクラスを作成します。ここではHowdyTalkyLoaderという名前を付けました。

public HowdyTalkyLoader() {
    ServiceLoader<HowdyTalky> loader = ServiceLoader.load(HowdyTalky.class);

    for (HowdyTalky talky: loader) {
        System.out.println(talky.getClass());
        System.out.println(talky.sayHello());
    }

}

ServiceLoaderクラスは直接newで生成することはできません。生成するにはloadメソッド,もしくはloadInstalledメソッドを使用します。

loadメソッドは,loadメソッドの引数の型で修飾されたServiceLoaderオブジェクトです。ServiceLoaderクラスは,クラスパス上に存在するMETA-INF/servicesの中から,引数で指定された型と同じファイルを探し,インタフェースの実装クラスを見つけ出します。

あとは,SerciveLoaderクラスからiterationメソッドでイテレーションを取り出すか,拡張for文で実装クラスを取り出せます。

では実行してみましょう。

C:\temp>java -cp talky.jar;. HowdyTalkyLoader
class HowdyTalky_en
Hello, World!
 
C:\temp>

ちゃんとWEB-INF/services/HowdyTalkyファイルに書いたようにHowdyTalky_enクラスが実行されました。

それではHowdyTalkyファイルをHowdyTalky_jaに書き換えて実行してみましょう。

C:\temp>java -cp talky.jar;. HowdyTalkyLoader
class HowdyTalky_ja
こんにちは,世界
 
C:\temp>

こんどはHowdyTalky_jaが生成されました。

では,二つ並べてみたらどうでしょうか。

HowdyTalky_en
HowdyTalky_ja

二つ並べる場合には,1行に1クラスになるようにします。これで実行すると...

C:\temp>java -cp talky.jar;. HowdyTalkyLoader
class HowdyTalky_en
Hello, World!
class HowdyTalky_ja
こんにちは,世界
 
C:\temp>

ちゃんと二つのクラスが生成されました。同じように,HowdyTalky_enと書かれたHowdyTalkyファイルを使用したJARファイルと,HowdyTalky_jaと書かれたHowdyTalkyファイルを使用したJARファイルの二つをクラスパスに指定しても,二つのクラスのオブジェクトが正しく生成されます。

ちなみに,このサンプルでもわかるように,ServiceLoaderで生成できるのは,デフォルトコンストラクタ(引数のないコンストラクタ)を持つものだけです。

依存性を断ち切る

ところで,なぜ直接newでコンポーネントを生成してはいけないのでしょうか。例えば,HowdyTalky_enクラスを使うとわかっているのであれば,次のように直接記述してもいいのではないかということです。

HowdyTalky talky = new HowdyTalky_en();

しかし,この記述ではHowdyTalky_enクラスからHowdyTalky_jaクラスに変更する場合,ソースコードを変更しなくてはなりません。

そんなことでいちいちソースコードを変更したくはありません。ソースコードを変更したら,テストもしなおさなくてはなりませんし,変更の過程でバグが入ってしまうかもしれないからです。

できれば,実装クラスについて考えることなく,インタフェースだけで扱いたいですね。そのような場合に使われるのが,Factory Methodパターンというデザインパターンです。

ServiceLoaderクラスはファクトリ・メソッドを汎用に使えるようにしたものと考えることができます。DIコンテナほど汎用的ではありませんが,特定のコンポーネントの生成には威力を発揮します。

ServiceLoaderクラスは,Java SE 6でもいろいろなところで使われています。例を挙げてみましょう。

  • JDBC
  • Scriptエンジン
  • Annotationプロセサ
  • Attach API
  • Xalan

このようにコンポーネントを使った例はいろいろあります。DIを使うほどではないけれど,コンポーネントは可換にしたい場合にServiceLoaderクラスはピッタリです。

著者紹介 櫻庭祐一

横河電機 ネットワーク開発センタ所属。Java in the Box 主筆

今月の櫻庭

この時期になると何となく気になるのが,クリスマス。

実をいうと,櫻庭はクリスマスCDのコレクターなのです。数えたことがないので正確な枚数はわからないのですが,優に100枚はあるはず。

今年もDaryl Hall & John OatsやAimee Mannなどのクリスマスアルバムが発売されています。ということで,今年のクリスマスアルバムの櫻庭のお薦めは...

  • 癒されたいあなたには
    Keali'i Reichel "Maluhia"
  • しっとりとしたクリスマスを過したいあなたには
    Sara McLachlan "Wintersong"
  • ちょっと変ったのが好みというあなたには
    Bootsy Collins "Christmas Is 4 Ever"

いかがですか。なお,これを書いている時点では,James TaylorやBlackmore's Nightなど,聴いていないアルバムもあるので,お薦めが入れ替わる可能性はありますが,参考になればと思います。