Java SPI(Service Provider Interface)及应用

参考

  • Java Service Provider Interface
  • Difference between SPI and API?

1. 概述

Java 6引入了一项功能,用于发现和加载与给定接口匹配的实现:服务提供者接口(SPI)。

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

2. Java SPI的术语和定义

Java SPI定义了四个主要组件

2.1 Service

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

2.2 服务提供者接口

充当服务的代理或终结点的接口或抽象类。 如果服务是一个接口,则它与服务提供者接口相同。 服务和SPI一起在Java生态系统中被称为API。

2.3 服务提供者

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

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

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

2.4. ServiceLoader

SPI的核心是ServiceLoader类。这具有延迟发现和加载实现的作用。它使用上下文类路径定位提供程序实现,并将其放入内部缓存中。

3. Java生态系统中的SPI示例

Java提供了许多SPI。以下是服务提供者接口及其提供的服务的一些示例:

  • CurrencyNameProvider:为Currency类提供本地化的货币符号。
  • LocaleNameProvider:提供Locale类的本地化名称。
  • TimeZoneNameProvider:为TimeZone类提供本地化的时区名称。
  • DateFormatProvider:提供指定语言环境的日期和时间格式。
  • NumberFormatProvider:为NumberFormat类提供货币,整数和百分比值。
  • Driver::从4.0版开始,JDBC API支持SPI模式。较旧的版本使用Class.forName()方法加载驱动程序。
  • PersistenceProvider:提供JPA API的实现。
  • JsonProvider:提供JSON处理对象。
  • JsonbProvider:提供JSON绑定对象。
  • Extension:提供CDI容器的扩展。
  • ConfigSourceProvider:提供用于检索配置属性的源。

4. 示例:货币汇率应用程序

现在我们了解的基础知识,让我们描述了建立一个汇率应用程序所需的步骤。

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

在4.1小节,我们将介绍服务时,SPI,并通过模块exchange-rate-api的的ServiceLoader,然后在小节4.2。我们将实现exchange-rate-impl模块中我们的服务提者,最后,我们将一起小节4.3通过模块exchange-rate-app带来的一切。

事实上,因为我们需要为服务提供者,让他们在模块exchange-rate-app内的类路径中,我们可以提供尽可能多的模块。

4.1 建立我们的API

我们首先创建一个名为exchange-rate-api的Maven项目。优良做法是,名称以api结尾,但是我们可以将其命名为“ api”。 然后,我们创建一个领域类来表示费率货币:

package com.baeldung.rate.api;

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

然后,我们通过创建接口QuoteManager定义用于检索报价的服务:

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();
    }
}

现在,我们已经有了用于获取所有已安装实现的服务,我们可以在客户端代码中使用所有这些实现来扩展我们的应用程序,或者通过选择一个首选实现来仅扩展一个应用程序。

请注意,该实用程序类不需要成为api项目的一部分。客户端代码可以选择调用ServiceLoader方法本身。

4.2 建立服务提供者

现在,我们创建一个名为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

4.3 把它放在一起

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

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

既然我们已经通过明确定义的步骤探索了Java SPI机制,那么应该很清楚地看到如何使用Java SPI创建易于扩展或可替换的模块。

尽管我们的示例使用Yahoo汇率服务展示了插入其他现有外部API的强大功能,但生产系统无需依赖第三方API即可创建出色的SPI应用程序。

像往常一样,可以在Github上找到该代码。

创作地址

  • Java SPI(Service Provider Interface)及应用

你可能感兴趣的:(java)