JigsawでSPIを使用する
Java9では,ようやくJigsawが導入され,モジュール化ができるようになります.
一方,SPI(SurviceProviderInterface)と呼ばれる仕組みがあり,広く利用されています.
今回は,JigsawでSPIを扱うための方法を紹介します.
Jigsawの基礎知識はある程度あるものと想定します.
もしも分からない部分があれば,櫻庭さんの記事*1 *2 *3 *4 *5 *6が分かりやすくまとまっているので,そちらをご参照ください.
SPIとは
SPIはSurviceProviderIntefaceの略で,あるプログラムやライブラリに対して,第三者が実装を提供するため仕組みです.
SPIはJDBCやPluggable Annotation Processing APIなど,多くの場面で利用されています.
ライブラリはSPIとなるインターフェースを提供し,java.util.ServiceLoaderを用いて,第三者の実装を読み込みます.
第三者はライブラリが提供するインターフェースを実装し,所定の方法でパッケージングし,クラスパスに指定します.
今までは,実装を提供する第三者は.jarファイルのMETA-INF/services内にインターフェースのFQCNが名前のファイルを作成し,その中に一行づつ具象クラスのFQCNを記述します.
例を用いて説明しましょう.
例はIndex of /~shinyafox/jigsaw/example/ModuledSPIExample/においてあるので,そちらも参考にしてください.
SPI提供者
まず,以下がSPIとなるインターフェース,HelloInterfaceです.
挨拶文を返すメソッドhelloが宣言されます.
package com.example.hello.spi; public interface HelloInterface { String hello(); }
このSPIを利用するクラスが以下です.
ServiceLoader#loadを用いてクラスパス内からHelloInterfaceを実装するクラスを探します.
HelloInterfaceを実装する全てのクラスに対し,helloメソッドを呼び出して,その結果を出力しています.
package com.example.hello; import java.util.Iterator; import java.util.ServiceLoader; import com.example.hello.spi.HelloInterface; public class Main { public static void main(String... args) { Iterator<HelloInterface> i = ServiceLoader.load(HelloInterface.class).iterator(); while (i.hasNext()) { HelloInterface hi = i.next(); String hello = hi.hello(); System.out.println(hello); } } }
これらは以下のようなファイル構造でhello.jarにパッケージングされています.
hello.jar └com └example └hello ├spi │└HelloInteface.class └Main.class
SPI実装者
さて,SPIを実装するクラスは以下のようになります.
package jp.example.hello; import com.example.hello.spi.HelloInterface; public class JapaneseHello implements HelloInterface { public String hello() { return "こんにちは"; } }
以下のファイル構造で,JapaneseHelloクラスをJARファイルとしてhello_jp.jarにパッケージングします.
hello_jp.jar ├jp │└example │ └hello │ └JapaneseHello.class └META-INF └services └com.example.hello.spi.HelloInterface
/META-INF/services/com.example.hello.spi.HelloInterface内に,このインターフェースを実装するクラスのFQCNを記述することで,ServiceLoaderで読み込むクラスの対象にできます.
JapaneseHelloを読み込ませるため,/META-INF/services/com.example.hello.spi.HelloInterfaceは以下の内容になります.
jp.example.hello.JapaneseHello
複数のクラスをServiceLoaderで読み込む対象とする場合は,改行して複数のクラスを列挙します.
実行
hello_jp.jarをクラスパスに与えて,hello.jarを実行すると,以下の出力が得られます.
$ java -cp "hello_jp.jar:hello.jar" com.example.hello.Main こんにちは
ここまでが従来のSPIの取り扱い方です.
JigsawでSPIを利用する
ここからはJigsawでSPIを利用する方法を紹介します.
SPI提供者
SPIをモジュール化して提供するには,module-info.java内でexports節で提供するSPIを含むパッケージを指定し,公開します.
同時に,uses節を用いて提供するSPIを指定します.
先ほどのhello.jarを用いて例示します.
実装は同じで,JARファイルの構造が異なります.
hello.jar ├com │└example │ └hello │ ├spi │ │└HelloInteface.class │ └Main.class └module-info.class
module-info.classが加わってることに注意してください.
module-info.classはmodule-info.javaをコンパイルして生成されたファイルです.
このファイルがあることで,hello.jarは名前付きモジュールとして扱われます.
module-info.javaを以下に示します.
module com.example.hello { exports com.example.hello; exports com.example.hello.spi; // SPIであるHelloInterfaceを可視にする uses com.example.hello.spi.HelloInterface; // HelloInterfaceがSPIであることを指示 }
ポイントはexports節でHelloInterfaceを含むcom.example.hello.spiが指定されることと,uses節でHelloInterfaceが指定されることです.
SPI実装者
SPIを実装するクラスを含むJARパッケージをモジュール化する時は,module-info.java内のprovides節でSPIを実装するクラスを指定します.
provides節の構文は「provides SPIとなるインターフェースのFQCN with 実装クラスのFQCN」です.
これも先ほどの例を用いて説明します.
実装は先程の例と同じで,hello_jp.jarのファイル構造が異なります.
hello_jp.jar ├jp │└example │ └hello │ └JapaneseHello.class └module-info.class
/META-INF/services以下が無くなり,module-info.classに変わったことに注意してください.
module-info.classの元となるmodule-info.javaを以下に示します.
module jp.example.hello { requires com.example.hello; exports jp.example.hello; provides com.example.hello.spi.HelloInterface with jp.example.hello.JapaneseHello; }
provides節で,HelloInterfaceの実装クラスとしてJapaneseHelloが指定されています.
今回の例では違いを分かりやすくするために,/META-INF/servicesを削除しましたが,互換性のために,それらを残すことも可能です.
特に,URLClassLoaderを用いて実装クラスを読み込む場合に,読み込む側の実装が適切でないと,/META-INF/services以下が無い場合は実装クラスが読み込まれません.
これについては別の記事で紹介しますが,互換性のために/META-INF/services以下を残しておくことをオススメします.
実行
それでは実行しましょう.
実行はhello.jarとhello_jp.jarをモジュールとして読み込むように設定し,com.example.helloモジュールのcom.example.hello.Mainを実行するクラスとして指定します.
$ java -mp "hello_jp.jar:hello.jar" -m com.example.hello/com.example.hello.Main こんにちは
ご覧のようにして,JigsawでSPIを扱うことができました.
*1:http://itpro.nikkeibp.co.jp/atcl/column/15/120700278/040500010/?ST=system
*2:http://itpro.nikkeibp.co.jp/atcl/column/15/120700278/042000011/?ST=system
*3:http://itpro.nikkeibp.co.jp/atcl/column/15/120700278/050900012/?ST=system
*4:http://itpro.nikkeibp.co.jp/atcl/column/15/120700278/052400013/?ST=system
*5:http://itpro.nikkeibp.co.jp/atcl/column/15/120700278/060800014/?ST=system
*6:http://itpro.nikkeibp.co.jp/atcl/column/15/120700278/062200015/?ST=system