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

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

1. 概要

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

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

2. JavaSPIの用語と定義

Java SPIは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クラスの金額、整数、およびパーセンテージの値を提供します。

  • Driver:バージョン4.0以降、JDBCAPIはSPIパターンをサポートします。 古いバージョンでは、Class.forName()メソッドを使用してドライバーをロードします。

  • PersistenceProvider:は、JPAAPIの実装を提供します。

  • JsonProvider:はJSON処理オブジェクトを提供します。

  • JsonbProvider:はJSONバインディングオブジェクトを提供します。

  • Extention:は、CDIコンテナの拡張機能を提供します。

  • ConfigSourceProvider:は、構成プロパティを取得するためのソースを提供します。

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

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

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

In sub-section 4.1., we’ll cover the Service, the SPI, and the ServiceLoaderからモジュールexchange-rate-api,まで、次にサブセクション4.2。 service *provider*the exchange-rate-implモジュールに実装し、最後に、サブセクション4.3からモジュールexchange-rate-appまですべてをまとめます。

実際、serviceproviderに必要な数のモジュールを提供し、それらをモジュールexchange-rate-app.のクラスパスで使用できるようにすることができます。

4.1. APIの構築

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

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

package com.example.rate.api;

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

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

package com.example.rate.api

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

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

package com.example.rate.spi;

public interface ExchangeRateProvider {
    QuoteManager create();
}

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

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

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

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

Iterator = loader.iterator();

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

Iterator = loader.reload();

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

public class ExchangeRate {

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

    public Iterator providers(boolean refresh) {
        if (refresh) {
            loader.reload();
        }
        return loader.iterator();
    }
}

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

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

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

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


    com.example
    exchange-rate-api
    1.0.0-SNAPSHOT

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

public class YahooFinanceExchangeRateProvider
  implements ExchangeRateProvider {

    @Override
    public QuoteManager create() {
        return new YahooQuoteManagerImpl();
    }
}

そしてここにQuoteManagerインターフェースの実装があります:

public class YahooQuoteManagerImpl implements QuoteManager {

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

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

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

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

com.example.rate.impl.YahooFinanceExchangeRateProvider

4.3. それを一緒に入れて

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


    com.example
    exchange-rate-api
    1.0.0-SNAPSHOT

この時点で、アプリケーションから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.example.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.example.rate.app.MainApp

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

5. 結論

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

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

いつものように、コードはover on Githubで見つけることができます。