【Java Gold】SPI（Service Provider Interface）について

## はじめに
JavaのSPI（Service Provider Interface）について、試験対策として整理しました。

## SPIとは

SPIとは「Service Provider Interface」の略で、**サービスを提供する側が実装するインターフェース**です。

利用者（クライアント）はその**具象クラス（実装クラス）を知らなくても、インターフェースだけを通じてサービスを利用**できます。

例として、JDBCの `java.sql.Driver` インターフェースがあります。これは代表的なSPIであり、MySQLやPostgreSQLなどのJDBCドライバがこのインターフェースを実装しています。

---

## SPIの構成要素

- **サービスインターフェース（SPI）**：サービス提供者が実装すべきインターフェースや抽象クラス
- **サービスプロバイダ**：SPIを実装した具象クラス
- **ServiceLoaderクラス**：実行時にサービスプロバイダを検索・読み込む仕組み


## モジュールを使わない実装方法

### 1. サービスインターフェースの定義

```java
public interface GreetingService {
    String greet(String name);
}
```

### 2. サービス実装クラスの作成

```java
public class EnglishGreetingService implements GreetingService {
    public String greet(String name) {
        return "Hello, " + name;
    }
}
```

このクラスは必ず `public` で、**引数なしのデフォルトコンストラクタ**を持っている必要があります。

`ServiceLoader` によって自動的にインスタンス化されるためです。

### 3. サービス定義ファイルの配置

プロジェクトの `META-INF/services/` ディレクトリに、

サービスインターフェースの完全修飾クラス名（FQCN）をファイル名としたファイルを作成し、

その中に実装クラスのFQCNを書きます。

### 例：

- ファイルパス：
    
    ```
    META-INF/services/com.example.GreetingService
    ```
    
- ファイルの内容：
    
    ```
    com.example.EnglishGreetingService
    ```
    

※ 完全修飾クラス名（FQCN）とは、クラスのパッケージ名とクラス名をピリオドで繋げたものです（例：`java.util.List`）。

### 4. クライアントコード

```java
ServiceLoader<GreetingService> loader = ServiceLoader.load(GreetingService.class);
for (GreetingService service : loader) {
    System.out.println(service.greet("wakame"));
}

```

`ServiceLoader` はクラスパス上の `META-INF/services` を探索し、対応するサービス実装を動的に読み込みます。


## モジュールを使う実装方法

モジュールシステムを使うと、SPIの構成と動作を**より厳密かつ明確に**制御できます。

以下は3つのモジュールに分けて構成します。

| モジュール名 | 役割 |
| --- | --- |
| `greeting.api` | サービスインターフェースの定義 |
| `greeting.impl` | 実装の提供と登録 |
| `app` | サービスの利用 |

### モジュール1：greeting.api（インターフェース定義）

```java
package com.example.spi;

public interface GreetingService {
    String greet(String name);
}

```

### module-info.java

```java
module greeting.api {
    exports com.example.spi;
}

```

- `exports` は、**他のモジュールからこのパッケージの中身（クラスなど）を見えるようにする**ための宣言です。
    
    これを記述しないと、他のモジュールから `GreetingService` を利用できません。
    

---

### モジュール2：greeting.impl（サービスの実装と提供）

```java
package com.example.impl;

import com.example.spi.GreetingService;

public class HelloGreetingService implements GreetingService {
    public String greet(String name) {
        return "Hello, " + name;
    }
}

```

### module-info.java

```java
module greeting.impl {
    requires greeting.api;
    provides com.example.spi.GreetingService
        with com.example.impl.HelloGreetingService;
}

```

- `requires greeting.api;`
    
    → このモジュールが `greeting.api` モジュールに依存していることを示します。つまり、`GreetingService` を使うにはこの宣言が必要です。
    
- `provides ... with ...;`
    
    → `GreetingService` に対する実装クラス（`HelloGreetingService`）を **Javaのモジュールシステムに登録**します。
    
    これにより、`ServiceLoader` がモジュールシステムを通じてこの実装を見つけられるようになります。
    

---

### モジュール3：app（サービスを利用するクライアント）

```java
package com.example.app;

import com.example.spi.GreetingService;
import java.util.ServiceLoader;

public class Main {
    public static void main(String[] args) {
        ServiceLoader<GreetingService> loader = ServiceLoader.load(GreetingService.class);
        for (GreetingService service : loader) {
            System.out.println(service.greet("wakame"));
        }
    }
}
```

### module-info.java

```java
module app {
    requires greeting.api;
    uses com.example.spi.GreetingService;
}
```

- `requires greeting.api;`
    
    → `GreetingService` を利用するためには、その定義があるモジュールへの依存を明示する必要があります。
    
- `uses com.example.spi.GreetingService;`
    
    → このモジュールが `GreetingService` を動的に使う（`ServiceLoader` を使う）ことをモジュールシステムに伝えます。
    
    これがないと、モジュールシステムは他モジュールの `provides` を探索してくれません。
    


## モジュールを使う場合のメリット

| 項目 | 内容 |
| --- | --- |
| 依存関係の明確化 | `requires`, `uses`, `provides` によって関係がコード上に表現される |
| セキュリティ | `exports` で必要なAPIだけを外部に公開できる |
| 最適化 | `jlink` などのツールによって不要なモジュールを省いた軽量化が可能 |
| 柔軟性 | 古い `META-INF/services` 方式との併用も可能 |


## モジュールあり・なしの比較表

| 比較項目 | モジュールなし | モジュールあり |
| --- | --- | --- |
| 対応バージョン | Java 8以前から | Java 9以降 |
| 構成 | クラスパスとサービス定義ファイル | モジュールと `module-info.java` による宣言 |
| 依存関係の記述 | 暗黙的（ファイルで記述） | 明示的（`requires`, `provides`, `uses`） |
| 柔軟性 | 高いが依存が見えにくい | 設計が明確、堅牢な構成に向いている |

---

## SPIを活用する利点

- 実装の切り替えが容易（プラグインのように扱える）
- クライアント側は実装を意識せずにサービスを利用可能（疎結合）
- Javaの標準API（JDBC、JAXP、ログAPIなど）でも広く採用されている


## まとめ

- SPI（Service Provider Interface）は、**サービスの提供者が実装し、利用者はその実装を意識せずに使用できるインターフェースの仕組み**です
- **実装と利用を分離**することで、柔軟で拡張性の高いアーキテクチャを実現できます
- SPIの実装には、**モジュールを使用する方法**と**モジュールを使用しない方法**の2通りがあります


## おわりに

誤記や改善点があればコメントなどでご指摘ください。