java spi学习;示例;

概述

Java 6引入了一个用于发现和加载与给定接口匹配的实现的特性:服务提供者接口(Service Provider interface, SPI)。

在本文,我们将介绍Java SPI的组件,并展示如何将其应用到实际的用例中。

Java SPI的术语和定义

Java SPI定义了四个主要组件

  1. service

    一组编程接口和类,它们提供对某些特定应用程序功能或特性的访问。

  2. service provider interface

    充当服务的代理或端点的接口或抽象类。

    如果服务是一个接口,则它与服务提供者接口相同。

    在Java生态系统中,服务和SPI一起被称为API。

  3. service provider

    SPI的特定实现。服务提供者包含一个或多个实现或扩展服务类型的具体类。

    服务提供者是通过提供者配置文件进行配置和标识的,我们将该配置文件放在资源目录META-INF/services中。文件名是SPI的完全限定名,其内容是SPI实现的完全限定名。

    服务提供程序是以扩展的形式安装的,我们将其放在应用程序类路径、java扩展类路径或用户定义类路径中的jar文件中。

  4. serviceLoader

    SPI的核心是ServiceLoader类。它的作用是延迟地发现和加载实现。它使用上下文类路径来定位提供者实现,并将它们放在内部缓存中。

Java中的SPI示例

Java提供了很多spi,下面是一些服务提供者接口和它提供的服务的示例:

CurrencyNameProvider: provides localized currency symbols for the Currency class.
LocaleNameProvider: provides localized names for the Locale class.
TimeZoneNameProvider: provides localized time zone names for the TimeZone class.
DateFormatProvider: provides date and time formats for a specified locale.
NumberFormatProvider: provides monetary, integer and percentage values for the NumberFormat class.
Driver: as of version 4.0, the JDBC API supports the SPI pattern. Older versions uses the Class.forName() method to load drivers.
PersistenceProvider: provides the implementation of the JPA API.
JsonProvider: provides JSON processing objects.
JsonbProvider: provides JSON binding objects.
Extention: provides extensions for the CDI container.
ConfigSourceProvider: provides a source for retrieving configuration properties.

示例:一个货币汇率应用程序

接下来让我们描述设置汇率应用程序所需的步骤。

为了突出这些步骤,我们需要使用至少三个项目:exchange-rate-api、exchange-rate-impl和exchange-rate-app。

在4.1小节,通过模块exchange-rate-api介绍service、SPI和ServiceLoader,
然后在第4.2小节,在exchange-rate-impl模块中实现服务提供商,
最后,我们将通过exchange-rate-app模块将所有内容整合到第4.3小节中。

创建API

首先创建一个名为exchange-rate-api的maven项目。

然后创建一个模型类来表示汇率和货币:

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

然后通过创建接口QuoteManager来定义检索报价的服务:

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

接下来,为服务创建一个SPI:

public interface ExchangeRateProvider {
    QuoteManager create();
}

最后,我们需要创建一个可由客户端代码使用的实用工具类exchangerat .java。这个类委托给ServiceLoader。

首先,调用静态工厂方法load()来获得ServiceLoader的一个实例:

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

然后调用iterate()方法来搜索和检索所有可用的实现。

Iterator<ExchangeRateProvider> = loader.iterator();

搜索结果被缓存,因此我们可以调用ServiceLoader.reload()方法来发现新安装的实现:

Iterator = 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方法本身。

构建服务提供者

现在创建一个名为exchange-rate-impl的Maven项目,并将API依赖项添加到pom.xml:

<dependency>
    <groupId>com.baeldunggroupId>
    <artifactId>exchange-rate-apiartifactId>
    <version>1.0.0-SNAPSHOTversion>
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

整合

最后,创建一个名为exchange-rate-app的客户端项目,并将dependency exchange-rate-api添加到类路径中:

<dependency>
    <groupId>com.baeldunggroupId>
    <artifactId>exchange-rate-apiartifactId>
    <version>1.0.0-SNAPSHOTversion>
dependency>

此时,我们可以从我们的应用程序调用SPI:

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

运行应用程序

现在集中精力构建所有的模块:

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

现在,we’ll include our provider in java.ext.dirs extension我们再次运行应用程序:

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

结论

现在,我们已经通过定义良好的步骤研究了Java SPI机制,应该可以清楚地看到如何使用Java SPI创建易于扩展或可替换的模块。

虽然我们的示例使用了Yahoo exchange rate服务来展示插入到其他现有外部api的强大功能,但是生产系统不需要依赖第三方api来创建优秀的SPI应用程序。

代码可以在Github上找到。

你可能感兴趣的:(java)