Javaではライブラリはjarで配布される場合が多い。その中でもインターフェイスと実装をjar単位で分離して、サードパーティによる実装の拡張が容易になるような仕組みが"SPI"(Service Provider Interface)と呼ばれており、JDK5までは "sun.misc.Service" という非公開クラスとメソッドだったのが、JDK6以降は "java.util.ServiceLoader" クラスとして公開され、利用できるようになった。
SPIの良い例がJDBCの処理で、インターフェイスは定義しておき、それを実装するライブラリを、DBMSごとに用意する。プログラマはSPI経由でJDBCインターフェイスを取得すれば、実際のどのDBMSの実装クラスが使われたのかを意識せずに、すべて同じインターフェイス(クラス名・メソッド名)でDB接続を操作できる。なおアプリを配布する際は、使用するDBMSのJDBC用jarファイルも一緒に配布する。
簡単なユースケースを以下のAntプロジェクトで試してみた:
仕組みとしては、実装側のjarファイルの "META-INF/services/" の下に、実装したいインターフェイスのクラス名でファイルを作成し、その中に実装ファイルのクラスを一行一個で記載する。
今回のサンプルでは、FooProviderが "spidemo.cloud.spi.Cloud" と "spidemo.search.spi.Search" の2つのインターフェイスの実装を提供するため、"META-INF/services/"に以下の2ファイルを配置している。
FooProvider/src/META-INF/services/spidemo.cloud.spi.Cloud:
spidemo.cloud.FooCloud
FooProvider/src/META-INF/services/spidemo.search.spi.Search:
spidemo.search.FooSearch
また、BarProviderでは "spidemo.search.spi.Search" に対して2つの実装クラスを提供するため、 以下のように2行にして2つのクラス名を記述している。
BarProvider/src/META-INF/services/spidemo.search.spi.Search:
spidemo.search.BarSearch spidemo.search.BarSearch2
mainクラスはDemoAppに配置しているが、DemoAppそれ自身でもCloudとSearchの両インターフェイスを実装して、自分自身の実装をロードできるか確認する。
DemoApp/src/META-INF/services/spidemo.cloud.spi.Cloud:
spidemo.cloud.MyCloud
DemoApp/src/META-INF/services/spidemo.search.spi.Search:
spidemo.search.MySearch
実行時の出力(解説付き):
# CloudService インターフェイスの実装としてFooProvider, BazProviderの2つを用意した。 # また、DemoApp自身が "My Cloud Provider" という名前の実装を含んでいる。 # getProviderName()とgetServiceNames()をprintしている。 Provider Name: My Cloud Provider My Container My Tomcat Provider Name: Foo Cloud Provider Foo VPC Foo VPN Foo Shared Server Foo Dedicated Server Provider Name: Baz Cloud Provider Baz Xen Computing Baz Security Gateway Baz Shared Storage # SearchService インターフェイスの実装としてFooProvider, BarProviderの2つを用意した。 # (なお、FooProviderはCloudとSearchの2つとも実装している) # また、DemoApp自身が "My Cloud Provider" という名前の実装を含んでいる。 Provider Name: My Search Provider My Search 1 My Search 2Provider Name: Foo Search Provider Foo Search 1 Foo Search 2 Foo Search 3 ## BarProviderでは、Search SPIに対して2つの実装を定義している。 Provider Name: Bar Search Provider Bar Search 1 Bar Search 2 Bar Search 3 Provider Name: Bar Search2 Provider Bar2 Search 1 Bar2 Search 2 Bar2 Search 3
参考:
日本語資料: