2018/08/14

Project Jigsawのちょっとしたクイズ

このエントリーをはてなブックマークに追加

前回のエントリーでも触れましたけど、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つ。

  1. 実装クラスがモジュールでないので、読み込めない
  2. モジュールパスで指定したディレクトリに実装クラスのJARファイルを配置して、自動モジュールとして読み込む
  3. クラスパスで指定して、無名モジュールとして読み込む
  4. モジュールパスでもクラスパスでも、どちらでも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 件のコメント: