GraalVM の native-image はモジュールには未対応ということで、モジュールによるアプリケーションのネイティブ化には一旦諦めましたが、モジュールの仕組みがまだよくわかってないので、もう少し勉強を続けることにした日本で 1,000,000 番目に Java のモジュールシステムに詳しいものです。
今回は Java9 で追加されていた ServiceLoader
のファクトリーメソッドによるオブジェクト生成について紹介します。
時間のない人のための 1 行まとめ
- Java9 のモジュールシステムによって、
ServiceLoader
は生成するオブジェクトを従来の引数なしコンストラクターだけでなく、provide ... with ...
ディレクティブに指定されたクラスのproviders
という static ファクトリーメソッドによって生成できるようになっていた
で、いつもどおりですが、詳しくは ServiceLoader
の javadoc を読むのがおすすめです。
従来の ServiceLoader
ServiceLoader
は provider configuration と呼ばれるファイルを作成して、中身に引数なしのコンストラクターを持つ実装クラスを指定しておくと、 ServiceLoader
が指定されたクラスを生成してくれる仕組みです。
例えば、インターフェース com.example.api.Message
に対して com.example.impl.Hello
という実装クラスを作成して、 META-INF/services/com.example.api.Message
というファイルの中に com.example.impl.Hello
と記入しておくと、 ServiceLoader.load(Message.class)
で ServiceLoader<Message>
を取り出して拡張 for 文でぶん回すと、 Hello
のインスタンスを利用できます。
com.example.api.Message.java
package com.example.api; public interface Message { String message(); }
com.example.impl.Hello.java
package com.example.impl; public class Hello implements Message { @Override public String message() { return "hello"; } }
META-INF/services/com.example.api.Message
com.example.impl.Hello
main
メソッドのあるクラス
import com.example.api.Message; class Main { public static void main(String[] args) { for (Message message: ServiceLoader.load(Message.class)) { System.out.println(message.message()); // 多分 hello と表示される } } }
コメントにも書いてあるとおり、このプログラムを実行すると hello
と表示されるはずです
モジュールによる ServiceLoader
これについては前回の記事にまとめてあります
モジュールによって追加された ServiceLoader
の static ファクトリーメソッドによるインスタンス生成方法
static なファクトリーメソッド provider
という提供したいインターフェースを返すメソッドを持つクラスを provides ... with ...
ディレクティブの with ...
に指定すると、 ServiceLoader
から provider
メソッドを呼び出して生成されたインスタンスを取得できます。
先程の例のインターフェース com.example.api.Message
に対して次のようなクラスを作ります
com.example.factory.Factory.java
package com.example.factory; import com.example.api.Message; public class Factory { // (1) public static Message provider() { //(2) return () -> "factory"; } }
(1) Factory
クラスは Message
クラスとは次のメソッド以外には何の関係もありません
(2) public
static
な provider
という引数なしのメソッドで Message
のインスタンスを作成・返します
factory
の方の module-info.java
import com.example.factory.Factory; module lib.factory { requires lib.api; // (1) provides com.example.api.Message with Factory; // (2) }
(1) Message
インターフェースを参照する
(2) Message
の実装クラスと同じように Factory
クラスを with
に指定する
このようにして作成されたモジュールを含むように調整して、先程の Main
クラスを実行すると factory
と出力されます。
疑問
ここで、一つ疑問を持ちます。インターフェース自体が、自分自身のデフォルトなインスタンスを返す static メソッド provider
を持った上で、 provides ... with ...
で自分自身を指定した場合、そのデフォルト実装が返されるのか?
実験してみましょう。
先程の Message
インターフェースに static で名前が provider
という引数なし・ Message
のインスタンスを返すメソッドを追加します。そして、同じモジュールで provides ... with ...
ディレクティブに自分自身を指定します。
com.example.api.Message.java
package com.example.api; public interface Message { String message(); static Message provider() { // (1) return () -> "message"; } }
(1) 名前が provider
な引数なし、 Message
を返す static メソッドを追加する
module-info.java
module lib.api {
exports com.example.api;
provides Message
with Message; // (1)
}
(1) 自分自身の提供するクラスを指定するのに自分自身を用います
このようにして作成されたモジュールを含むように調整して、先程の Main
クラスを実行すると factory
とともに message
が出力されます(なお、出力される順番が何によって定まるのかはわかりません)。
レポジトリー
ここまでの内容のプロジェクトを、 gradle で組むのは面倒かと思いますので、今回もリポジトリーを作ってあります。
モジュールのものを動かす場合は runModuleApp
を起動してください。
同じ内容になるように provider configuration を構成した jar プロジェクトも作成してありますが、こちらは provider()
メソッドには対応していないので、実行時にエラーになります。