1. 概要

Java 6では、特定のインターフェースに一致する実装を検出してロードするための機能、サービス・プロバイダー・インターフェース(SPI)が導入されました。

このチュートリアルでは、Java SPIのコンポーネントを紹介し、それを実際のユースケースに適用する方法を示します。

2. JavaSPIの用語と定義

JavaSPIは4つの主要コンポーネントを定義します

2.1. サービス

特定のアプリケーション機能または機能へのアクセスを提供する、よく知られたプログラミングインターフェイスとクラスのセット。

2.2. サービスプロバイダーインターフェイス

サービスのプロキシまたはエンドポイントとして機能するインターフェイスまたは抽象クラス。

サービスが1つのインターフェイスである場合、それはサービスプロバイダーインターフェイスと同じです。

サービスとSPIは、JavaエコシステムではAPIとしてよく知られています。

2.3. サービスプロバイダー

SPIの特定の実装。 サービスプロバイダーには、サービスタイプを実装または拡張する1つ以上の具象クラスが含まれています。

サービスプロバイダーは、リソースディレクトリ META-INF /servicesに配置されたプロバイダー構成ファイルを介して構成および識別されます。 ファイル名はSPIの完全修飾名であり、その内容はSPI実装の完全修飾名です。

サービスプロバイダーは、拡張機能、アプリケーションクラスパス、Java拡張機能クラスパスまたはユーザー定義クラスパスに配置するjarファイルの形式でインストールされます。

2.4. ServiceLoader

SPIの中心には、ServiceLoaderクラスがあります。 これには、実装を怠惰に発見してロードする役割があります。 コンテキストクラスパスを使用してプロバイダーの実装を見つけ、それらを内部キャッシュに配置します。

3. JavaエコシステムのSPIサンプル

Javaは多くのSPIを提供します。 以下に、サービスプロバイダーインターフェイスとそれが提供するサービスのサンプルをいくつか示します。

  • CurrencyNameProvider:は、Currencyクラスのローカライズされた通貨記号を提供します。
  • LocaleNameProvider:は、Localeクラスのローカライズされた名前を提供します。
  • TimeZoneNameProvider:は、TimeZoneクラスのローカライズされたタイムゾーン名を提供します。
  • DateFormatProvider:は、指定されたロケールの日付と時刻の形式を提供します。
  • NumberFormatProvider:は、 NumberFormat クラスの金額、整数、およびパーセンテージの値を提供します。
  • ドライバー:バージョン4.0以降、JDBCAPIはSPIパターンをサポートします。 古いバージョンでは、 Class.forName()メソッドを使用してドライバーをロードします。
  • PersistenceProvider:は、JPAAPIの実装を提供します。
  • JsonProvider:はJSON処理オブジェクトを提供します。
  • JsonbProvider:はJSONバインディングオブジェクトを提供します。
  • 拡張機能:はCDIコンテナの拡張機能を提供します。
  • ConfigSourceProvider:は、構成プロパティを取得するためのソースを提供します。

4. ショーケース:為替レートアプリケーション

基本を理解したところで、為替レートアプリケーションを設定するために必要な手順を説明しましょう。

これらの手順を強調するには、少なくとも3つのプロジェクトを使用する必要があります: exchange-rate-api exchange-rate-impl、exchange-rate-app。[X149X ]

サブセクション4.1。では、モジュール exchange-rate-api、を介して、サービス、SPI、およびServiceLoaderについて説明します。次にサブセクション4.2で説明します。 サービスプロバイダーexchange-rate-implモジュールに実装し、最後に、サブセクション4.3でモジュールexchange-を介してすべてをまとめます。 rate-app

実際、 se rvice プロバイダーに必要な数のモジュールを提供し、モジュールexchangeのクラスパスで使用できるようにすることができます-レートアプリ。

4.1. APIの構築

まず、exchange-rate-apiというMavenプロジェクトを作成します。 名前の末尾がapiであるのは良い習慣ですが、どのように呼んでもかまいません。

次に、レート通貨を表すためのモデルクラスを作成します。

package com.baeldung.rate.api;

public class Quote {
    private String currency;
    private LocalDate date;
    ...
}

次に、インターフェース QuoteManager:を作成して、見積もりを取得するためのServiceを定義します。

package com.baeldung.rate.api

public interface QuoteManager {
    List<Quote> getQuotes(String baseCurrency, LocalDate date);
}

次に、サービス用にSPIを作成します。

package com.baeldung.rate.spi;

public interface ExchangeRateProvider {
    QuoteManager create();
}

最後に、クライアントコードで使用できるユーティリティクラスExchangeRate.javaを作成する必要があります。 このクラスはServiceLoaderに委任します。

まず、静的ファクトリメソッド load()を呼び出して、 ServiceLoader:のインスタンスを取得します。

ServiceLoader<ExchangeRateProvider> loader = ServiceLoader .load(ExchangeRateProvider.class);

次に、 iterate()メソッドを呼び出して、使用可能なすべての実装を検索および取得します。

Iterator<ExchangeRateProvider> = loader.iterator();

検索結果はキャッシュされるため、 ServiceLoader.reload()メソッドを呼び出して、新しくインストールされた実装を検出できます。

Iterator<ExchangeRateProvider> = loader.reload();

そして、これが私たちのユーティリティクラスです:

public class ExchangeRate {

    ServiceLoader<ExchangeRateProvider> loader = ServiceLoader
      .load(ExchangeRateProvider.class);
 
    public Iterator<ExchangeRateProvider> providers(boolean refresh) {
        if (refresh) {
            loader.reload();
        }
        return loader.iterator();
    }
}

インストールされているすべての実装を取得するサービスができたので、クライアントコードでそれらすべてを使用して、アプリケーションを拡張するか、優先する実装を選択することで1つだけを拡張できます。

このユーティリティクラスは、apiプロジェクトの一部である必要はないことに注意してください。 クライアントコードは、ServiceLoaderメソッド自体を呼び出すことを選択できます。

4.2. サービスプロバイダーの構築

次に、 exchange-rate-impl という名前のMavenプロジェクトを作成し、API依存関係を pom.xml:に追加します。

<dependency>
    <groupId>com.baeldung</groupId>
    <artifactId>exchange-rate-api</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

次に、SPIを実装するクラスを作成します。

public class YahooFinanceExchangeRateProvider 
  implements ExchangeRateProvider {
 
    @Override
    public QuoteManager create() {
        return new YahooQuoteManagerImpl();
    }
}

そして、ここで QuoteManagerインターフェースの実装:

public class YahooQuoteManagerImpl implements QuoteManager {

    @Override
    public List<Quote> getQuotes(String baseCurrency, LocalDate date) {
        // fetch from Yahoo API
    }
}

検出するために、プロバイダー構成ファイルを作成します。

META-INF/services/com.baeldung.rate.spi.ExchangeRateProvider

ファイルの内容は、SPI実装の完全修飾クラス名です。

com.baeldung.rate.impl.YahooFinanceExchangeRateProvider

4.3. それを一緒に入れて

最後に、 exchange-rate-app というクライアントプロジェクトを作成し、依存関係のexchange-rate-apiをクラスパスに追加しましょう。

<dependency>
    <groupId>com.baeldung</groupId>
    <artifactId>exchange-rate-api</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

この時点で、アプリケーションからSPIを呼び出すことができます

ExchangeRate.providers().forEach(provider -> ... );

4.4. アプリケーションの実行

ここで、すべてのモジュールの構築に焦点を当てましょう。

mvn clean package

次に、プロバイダーを考慮せずにJavaコマンドを使用してアプリケーションを実行します。

java -cp ./exchange-rate-api/target/exchange-rate-api-1.0.0-SNAPSHOT.jar:./exchange-rate-app/target/exchange-rate-app-1.0.0-SNAPSHOT.jar com.baeldung.rate.app.MainApp

次に、プロバイダーを java .ext.dirs 拡張子に含め、アプリケーションを再度実行します。

java -Djava.ext.dirs=$JAVA_HOME/jre/lib/ext:./exchange-rate-impl/target:./exchange-rate-impl/target/depends -cp ./exchange-rate-api/target/exchange-rate-api-1.0.0-SNAPSHOT.jar:./exchange-rate-app/target/exchange-rate-app-1.0.0-SNAPSHOT.jar com.baeldung.rate.app.MainApp

プロバイダーがロードされていることがわかります。

5. 結論

明確に定義された手順でJavaSPIメカニズムを検討したので、JavaSPIを使用して簡単に拡張または交換可能なモジュールを作成する方法を理解する必要があります。

この例では、Yahooの為替レートサービスを使用して他の既存の外部APIにプラグインする能力を示しましたが、本番システムは、優れたSPIアプリケーションを作成するためにサードパーティのAPIに依存する必要はありません。

コードは、いつものように、Githubにあります。