【Java学習|豆知識】Javaモジュールシステムにおける「ServiceLoader」の活用法とprovides-with宣言

導入: なぜ「provides…with…」が重要なのか

Java 9で導入されたモジュールシステム(Project Jigsaw)において、疎結合な設計を実現するための鍵となるのが「サービス・プロバイダ・インターフェース(SPI)」です。通常、実装クラスを直接インスタンス化すると依存関係が強固になり、ライブラリの入れ替えが困難になります。`provides…with…`宣言を使用することで、実装クラスを隠蔽したまま、実行時に動的にサービスを注入できるようになり、保守性の高いアプリケーション構築が可能になります。

基礎知識: モジュールシステムとSPI

Javaのモジュールシステムにおいて、あるモジュールが機能を提供し、別のモジュールがそれを利用する仕組みを「サービス」と呼びます。
provides … with …は、モジュール記述子(module-info.java)において、「このモジュールは、指定されたサービスインターフェース(provides)に対して、この具体的な実装クラス(with)を提供します」と宣言するための構文です。これを利用することで、利用側は実装クラスを知らなくても、ServiceLoader経由で機能を呼び出せます。

実装/解決策: 手順のステップ

1. サービスとなるインターフェースを定義します。
2. 実装クラスを作成します。
3. モジュール記述子(module-info.java)で、`provides`句を使って実装を公開します。
4. 利用側(コンシューマ)のモジュール記述子で、`uses`句を使ってサービスを利用することを宣言します。

サンプルプログラム

まず、サービスを提供する側のモジュール設定と実装例です。

// サービスインターフェース(別モジュールでも可)
public interface GreetingService {
void sayHello();
}

// 実装クラス
public class ConsoleGreetingService implements GreetingService {
@Override
public void sayHello() {
System.out.println(“こんにちは、モジュールシステム経由のサービスです!”);
}
}

// モジュール記述子 (module-info.java)
module com.example.provider {
// インターフェースをエクスポート
exports com.example.service;
// 実装クラスをサービスとして提供
provides com.example.service.GreetingService
with com.example.impl.ConsoleGreetingService;
}

次に、利用側のコード例です。

// 利用側のモジュール記述子 (module-info.java)
module com.example.consumer {
requires com.example.provider;
uses com.example.service.GreetingService;
}

// 実行コード
import java.util.ServiceLoader;
import com.example.service.GreetingService;

public class Main {
public static void main(String[] args) {
// ServiceLoaderを使用して実装を動的にロードする
ServiceLoader loader = ServiceLoader.load(GreetingService.class);

for (GreetingService service : loader) {
service.sayHello();
}
}
}

応用・注意点: 現場での活用と落とし穴

注意点1: モジュールパスの管理
`provides`句で宣言しても、実行時にそのモジュールがモジュールパス(–module-path)に含まれていないと、`ServiceLoader`は実装を見つけることができません。クラスパスと混同しないよう注意が必要です。

注意点2: 実装クラスの公開
`provides`で指定する実装クラス自体は、必ずしも`exports`する必要はありません。`module-info`内で`provides`されていれば、カプセル化を維持したまま、`ServiceLoader`経由で外部モジュールから利用可能です。これにより、実装の詳細を外部から隠蔽する「設計の美しさ」が保たれます。

応用: 複数のプロバイダ
同じインターフェースに対して複数のプロバイダを`provides`で定義することも可能です。プラグインアーキテクチャを構築する際には、この仕組みが標準的な解決策となります。

コメント

タイトルとURLをコピーしました