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-api、exchange-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で見つけることができます。