前回のエントリーでも触れましたけど、Jigsawのモジュールには通常のモジュールと自動モジュール (Automatic Module)、無名モジュール (Unnamed Module)の3種類あります。
自動モジュールと無名モジュールはモジュールとは名前がついてますけど、普通のJARファイルと変わりません。
自動モジュールはモジュールパスで指定し、無名モジュールは従来通りクラスパスで指定します。
通常のモジュールがアクセスできるのは、通常のモジュールか自動モジュールだけ。無名モジュールにはアクセスできません。無名モジュールにアクセスできるのは、自動モジュールです。
ところで、みなさんはjava.util.ServiceLoaderクラスをご存知でしょうか。
ServiceLoaderクラスを使うと、指定したインタフェースの実装クラスを実行時にロードすることができます。いわゆるSPIを実現するためのクラスです。
もちろん、JigsawでもServiceLoaderクラスをサポートしてますが、従来の方法とは実装クラスの指定方法が変わっています。
今までは、インタフェースの実装クラスを提供する場合、JARファイルのMETA-INF/servicesディレクトリにインタフェースと同名のファイルを作成し、ファイルには実装クラス名を記述します。
たとえば、インタフェースがnet.javainthebox.hello.Helloインタフェースで、SPIで提供する実装クラスがnet.javainthebox.hello.impl.HelloImplクラスだったとします。
この場合、META-INF/services/net.javainthebox.hello.Helloファイルを作成します。そして、net.javainthebox.hello.Helloファイルにはnet.javainthebox.hello.impl.HelloImplとだけ記述しておきます。
これでServiceLoaderは、クラスパスにあるJARファイルを調べて、Helloインタフェースの実装クラスをロードすることができました。
実装クラスをモジュールで提供する場合、インタフェースと同名のファイルを作成するのではなく、module-info.javaに記述します。
先ほどの例であれば、module-info.javaには次のように記述します。
module net.javainthebox.helloimpl { requires net.javainthebox.hello; provides net.javainthebox.hello.Hello with net.javainthebox.hello.impl.HelloImpl; }
provides文でインタフェースと実装クラスを記述するわけです。
ただし、後方互換性のためにmodule-info.javaに記述するだけでなく、META-INF/servicesディレクトリにインタフェースと同名のファイルを置いておいた方がいいと思います。
モジュールがこの実装クラスを使いたい場合、はmodule-info.javaにuses文でインタフェースを指定します。
たとえば、次のように記述します。
module net.javainthebox.helloclient { requires net.javainthebox.hello; // SPIで使用するインタフェース uses net.javainthebox.hello.Hello; }
ServiceLoaderクラスの使い方はまったく同じで、Helloインタフェースの実装クラスを探してロードすることができます。
ここでクイズです。
インタフェースは通常のモジュールで定義されています。インタフェースを使用するクライアントもモジュールです。
しかし、実装クラスがモジュールでない場合、ようするにMETA-INF/servicesディレクトリを使ったJARファイルの場合、どうすれば実装クラスを読み込むことができるでしょうか。
選択肢は4つ。
- 実装クラスがモジュールでないので、読み込めない
- モジュールパスで指定したディレクトリに実装クラスのJARファイルを配置して、自動モジュールとして読み込む
- クラスパスで指定して、無名モジュールとして読み込む
- モジュールパスでもクラスパスでも、どちらでもOK
正解は.....
選択肢4のモジュールパスでもクラスパスでもOKです。
通常のモジュールからのアクセスなので、モジュールパスに配置して自動モジュールとして扱わなくてはいけないように思えるかもしれません。でも、クラスパスでもOKなんです。
通常のモジュールが無名モジュールにアクセスできるという稀有な例なのでした。
いちおう、コードと実行例を示しておきます。
SPIで使用するHelloインタフェースはこんな感じ。
package net.javainthebox.hello; public interface Hello { public void hello(); }
module-info.javaは次の通り。
module net.javainthebox.hello { exports net.javainthebox.hello; }
実装クラスのHelloImplクラス。
package net.javainthebox.hello.impl; import net.javainthebox.hello.Hello; public class HelloImpl implements Hello { public void hello() { System.out.println("Hello, World!"); } }
HelloImplクラスを含んだJARファイルは、前述のようにMETA-INF/services/net.javainthebox.hello.Helloファイルを作成して、net.javainthebox.hello.impl.HelloImplと記述してあります。
さて、クライアントは。
package net.javainthebox.helloclient; import java.util.ServiceLoader; import net.javainthebox.hello.Hello; public class HelloClient { public static void main(String... args) { ServiceLoader<Hello> loader = ServiceLoader.load(Hello.class); for (Hello hello: loader) { hello.hello(); } } }
クライアントのmodule-info.javaは上の方に書いてありますね。
ビルドはやってもらうということで、実行してみます。
modディレクトリをモジュールパス、libディレクトリをクラスパス用に使用するとしましょう。
まずは、自動モジュールとして実行してみます。
C:\serviceclient>dir mod ドライブ C のボリューム ラベルがありません。 ボリューム シリアル番号は 4A4B-822F です C:\serviceclient\mod のディレクトリ 2018/08/14 21:58 <DIR> . 2018/08/14 21:58 <DIR> .. 2018/08/12 14:36 1,235 hello-api.jar 2018/08/12 20:01 1,603 hello-client.jar 2018/08/12 19:36 1,607 hello-impl.jar 3 個のファイル 4,445 バイト 2 個のディレクトリ 163,930,116,096 バイトの空き領域 C:\serviceclient>java -p mod -m net.javainthebox.helloclient/net.javainthebox.helloclient.HelloClient Hello, World! C:\serviceclient>
Hello, World!が表示されました。
次に、実装クラスのJARファイルをlibディレクトリに移動させて、クラスパスで指定してみましょう。
C:\serviceclient>mv mod\hello-impl.jar lib C:\serviceclient>java -p mod -cp lib\hello-impl.jar -m net.javainthebox.helloclient/net.javainthebox.helloclient.HelloClient Hello, World! C:\serviceclient>
クラスパスで指定しても、ちゃんと実行できています。
というわけで、ちょっとしたJigsawのクイズでした。
0 件のコメント:
コメントを投稿