Перейти к основному содержимому

Интерфейс поставщика услуг Java

· 6 мин. чтения

1. Обзор

В Java 6 появилась функция для обнаружения и загрузки реализаций, соответствующих заданному интерфейсу: интерфейс поставщика услуг (SPI).

В этом руководстве мы познакомим вас с компонентами Java SPI и покажем, как мы можем применить его на практике.

2. Термины и определения Java SPI

Java SPI определяет четыре основных компонента

2.1. обслуживание

Известный набор программных интерфейсов и классов, обеспечивающих доступ к некоторым конкретным функциям или функциям приложения.

2.2. Интерфейс поставщика услуг

Интерфейс или абстрактный класс, который действует как прокси или конечная точка для службы.

Если служба представляет собой один интерфейс, то она аналогична интерфейсу поставщика услуг.

Сервис и SPI вместе хорошо известны в экосистеме Java как API.

2.3. Поставщик услуг

Конкретная реализация SPI. Поставщик услуг содержит один или несколько конкретных классов, которые реализуют или расширяют тип службы.

Поставщик услуг настраивается и идентифицируется с помощью файла конфигурации поставщика, который мы помещаем в каталог ресурсов META-INF/services . Имя файла — это полное имя SPI, а его содержимое — это полное имя реализации SPI.

Поставщик услуг устанавливается в виде расширений, jar-файла, который мы помещаем в путь к классам приложения, путь к классам расширения Java или определяемый пользователем путь к классам.

2.4. Сервисный загрузчик

В основе SPI лежит класс ServiceLoader . Это играет роль ленивого обнаружения и загрузки реализаций. Он использует контекстный путь к классам, чтобы найти реализации провайдера и поместить их во внутренний кеш.

3. Образцы SPI в экосистеме Java

Java предоставляет множество SPI. Вот несколько примеров интерфейса поставщика услуг и услуг, которые он предоставляет:

  • CurrencyNameProvider: предоставляет локализованные символы валюты для класса Currency .
  • LocaleNameProvider : предоставляет локализованные имена для класса Locale .
  • TimeZoneNameProvider: предоставляет локализованные имена часовых поясов для класса TimeZone .
  • DateFormatProvider : предоставляет форматы даты и времени для указанной локали.
  • NumberFormatProvider : предоставляет денежные, целые и процентные значения для класса NumberFormat .
  • Драйвер: начиная с версии 4.0, JDBC API поддерживает шаблон SPI. Более старые версии используют метод Class.forName() для загрузки драйверов.
  • PersistenceProvider: обеспечивает реализацию JPA API.
  • JsonProvider: предоставляет объекты обработки JSON.
  • JsonbProvider: предоставляет объекты привязки JSON.
  • Расширение: предоставляет расширения для контейнера CDI.
  • ConfigSourceProvider : предоставляет источник для получения свойств конфигурации.

4. Витрина: приложение для курсов обмена валют

Теперь, когда мы познакомились с основами, давайте опишем шаги, необходимые для настройки приложения обменного курса.

Чтобы выделить эти шаги, нам нужно использовать как минимум три проекта: exchange-rate-api , exchange-rate-impl и exchange-rate-app.

В подразделе 4.1 мы рассмотрим Service , SPI и ServiceLoader через модуль exchange-rate-api, а затем в подразделе 4.2. мы реализуем нашего поставщика услуг в модуле exchange-rate-impl , и, наконец, мы соберем все воедино в подразделе 4.3 через модуль exchange-rate-app .

На самом деле мы можем предоставить столько модулей, сколько нам нужно для поставщика услуг , и сделать их доступными в пути к классам модуля exchange-rate-app.

4.1. Создание нашего API

Начнем с создания проекта Maven под названием exchange-rate-api . Хорошей практикой является то, что имя заканчивается термином api , но мы можем называть его как угодно.

Затем мы создаем класс модели для представления курсов валют:

package com.foreach.rate.api;

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

А затем мы определяем наш Сервис для получения котировок, создав интерфейс QuoteManager:

package com.foreach.rate.api

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

Далее мы создаем SPI для нашего сервиса:

package com.foreach.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();
}
}

Теперь, когда у нас есть служба для получения всех установленных реализаций, мы можем использовать их все в нашем клиентском коде для расширения нашего приложения или только одну, выбрав предпочтительную реализацию.

Обратите внимание, что этот служебный класс не обязательно должен быть частью проекта API . Клиентский код может сам вызывать методы ServiceLoader.

4.2. Создание поставщика услуг

Давайте теперь создадим проект Maven с именем exchange-rate-impl и добавим зависимость API к pom.xml:

<dependency>
<groupId>com.foreach</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.foreach.rate.spi.ExchangeRateProvider

Содержимое файла представляет собой полное имя класса реализации SPI:

com.foreach.rate.impl.YahooFinanceExchangeRateProvider

4.3. Собираем вместе

Наконец, давайте создадим клиентский проект с именем exchange-rate-app и добавим зависимость exchange-rate-api в путь к классам:

<dependency>
<groupId>com.foreach</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.foreach.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.foreach.rate.app.MainApp

Мы видим, что наш провайдер загружен.

5. Вывод

Теперь, когда мы изучили механизм Java SPI с помощью четко определенных шагов, должно быть ясно, как использовать Java SPI для создания легко расширяемых или заменяемых модулей.

Хотя в нашем примере использовалась служба обменных курсов Yahoo, чтобы продемонстрировать возможности подключения к другим существующим внешним API, производственным системам не нужно полагаться на сторонние API для создания отличных приложений SPI.

Код, как обычно, можно найти на Github .