Java Service Provider Interface – Java服务提供者接口

最后修改: 2018年 5月 4日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Java 6 has introduced a feature for discovering and loading implementations matching a given interface: Service Provider Interface (SPI).

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

In this tutorial, we’ll introduce the components of Java SPI and show how we can apply it to a practical use case.

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

2. Terms and Definitions of Java SPI

2.Java SPI的术语和定义

Java SPI defines four main components

Java SPI定义了四个主要部分

2.1. Service

2.1.服务

A well-known set of programming interfaces and classes that provide access to some specific application functionality or feature.

一组众所周知的编程接口和类,提供对某些特定应用功能或特性的访问。

2.2. Service Provider Interface

2.2.服务提供者接口

An interface or abstract class that acts as a proxy or an endpoint to the service.

一个接口或抽象类,作为代理或服务的端点。

If the service is one interface, then it is the same as a service provider interface.

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

Service and SPI together are well-known in the Java Ecosystem as API.

服务和SPI一起作为API在Java生态系统中是众所周知的。

2.3. Service Provider

2.3.服务提供者

A specific implementation of the SPI. The Service Provider contains one or more concrete classes that implement or extend the service type.

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

A Service Provider is configured and identified through a provider configuration file which we put in the resource directory META-INF/services. The file name is the fully-qualified name of the SPI and its content is the fully-qualified name of the SPI implementation.

一个服务提供者是通过一个提供者的配置文件来配置和识别的,我们把这个文件放在资源目录META-INF/services中。文件名是SPI的全称,其内容是SPI实现的全称。

The Service Provider is installed in the form of extensions, a jar file which we place in the application classpath, the Java extension classpath or the user-defined classpath.

服务提供者是以扩展的形式安装的,是一个jar文件,我们把它放在应用程序classpath、Java扩展classpath或用户定义的classpath中。

2.4. ServiceLoader

2.4.服务加载器

At the heart of the SPI is the ServiceLoader class. This has the role of discovering and loading implementations lazily. It uses the context classpath to locate provider implementations and put them in an internal cache.

SPI的核心是ServiceLoader类。它的作用是懒散地发现和加载实现。它使用上下文的classpath来定位提供者的实现,并把它们放在一个内部缓存中。

3. SPI Samples in the Java Ecosystem

3.Java生态系统中的SPI样本

Java provides many SPIs. Here are some samples of the service provider interface and the service that it provides:

Java提供了许多SPI。下面是一些服务提供者接口和它所提供的服务的例子。

4. Showcase: a Currency Exchange Rates Application

4.展示:一个货币汇率应用程序

Now that we understand the basics, let’s describe the steps that are required to set up an exchange rate application.

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

To highlight these steps, we need to use at least three projects: exchange-rate-api, exchange-rate-impl, and exchange-rate-app.

为了突出这些步骤,我们至少需要使用三个项目。exchange-rate-apiexchange-rate-implexchange-rate-app.

In sub-section 4.1., we’ll cover the Service, the SPI and the ServiceLoader through the module exchange-rate-api, then in sub-section 4.2. we’ll implement our service provider in the exchange-rate-impl module, and finally, we’ll bring everything together in sub-section 4.3 through the module exchange-rate-app.

在第4.1节中,我们将通过exchange-rate-api>模块介绍ServiceSPIServiceLoader。我们将在the exchange-rate-impl模块中实现我们的service provider,最后,我们将在4.3小节中通过exchange-rate-app模块将所有东西整合起来。

In fact, we can provide as many modules as we need for the service provider and make them available in the classpath of the module exchange-rate-app.

事实上,我们可以为service provider提供尽可能多的模块,并使它们在exchange-rate-app.模块的classpath中可用。

4.1. Building Our API

4.1.构建我们的API

We start by creating a Maven project called exchange-rate-api. It’s good practice that the name ends with the term api, but we can call it whatever.

我们首先创建一个名为exchange-rate-api的Maven项目。项目名称以api结尾是很好的做法,但我们可以随便叫它。

Then we create a model class for representing rates currencies:

然后我们创建一个模型类来代表汇率货币。

package com.baeldung.rate.api;

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

And then we define our Service for retrieving quotes by creating the interface QuoteManager:

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

package com.baeldung.rate.api

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

Next, we create an SPI for our service:

接下来,我们为我们的服务创建一个SPI

package com.baeldung.rate.spi;

public interface ExchangeRateProvider {
    QuoteManager create();
}

And finally, we need to create a utility class ExchangeRate.java that can be used by client code. This class delegates to ServiceLoader.

最后,我们需要创建一个实用类ExchangeRate.java,可以被客户端代码使用。这个类委托给ServiceLoader

First, we invoke the static factory method load() to get an instance of ServiceLoader:

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

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

And then we invoke the iterate() method to search and retrieve all available implementations.

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

Iterator<ExchangeRateProvider> = loader.iterator();

The search result is cached so we can invoke the ServiceLoader.reload() method in order to discover newly installed implementations:

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

Iterator<ExchangeRateProvider> = loader.reload();

And here’s our utility class:

而这里是我们的实用类。

public class ExchangeRate {

    ServiceLoader<ExchangeRateProvider> loader = ServiceLoader
      .load(ExchangeRateProvider.class);
 
    public Iterator<ExchangeRateProvider> providers(boolean refresh) {
        if (refresh) {
            loader.reload();
        }
        return loader.iterator();
    }
}

Now that we have a service for getting all installed implementations, we can use all of them in our client code to extend our application or just one by selecting a preferred implementation.

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

Note that this utility class is not required to be part of the api project. Client code can choose to invoke ServiceLoader methods itself.

注意,这个实用类不需要成为api项目的一部分。客户端代码可以选择自己调用ServiceLoader方法。

4.2. Building the Service Provider

4.2.构建服务提供者

Let’s now create a Maven project named exchange-rate-impl and we add the API dependency to the pom.xml:

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

<dependency>
    <groupId>com.baeldung</groupId>
    <artifactId>exchange-rate-api</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

Then we create a class that implements our SPI:

然后我们创建一个类,实现我们的SPI。

public class YahooFinanceExchangeRateProvider 
  implements ExchangeRateProvider {
 
    @Override
    public QuoteManager create() {
        return new YahooQuoteManagerImpl();
    }
}

And here the implementation of the QuoteManager interface:

这里是QuoteManager接口的实现。

public class YahooQuoteManagerImpl implements QuoteManager {

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

In order to be discovered, we create a provider configuration file:

为了被发现,我们创建一个提供者的配置文件。

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

The content of the file is the fully qualified class name of the SPI implementation:

该文件的内容是SPI实现的完全合格的类名。

com.baeldung.rate.impl.YahooFinanceExchangeRateProvider

4.3. Putting It Together

4.3.把它放在一起

Finally, let’s create a client project called exchange-rate-app and add the dependency exchange-rate-api to the classpath:

最后,让我们创建一个名为exchange-rate-app的客户端项目,并在classpath中添加 exchange-rate-api这个依赖项。

<dependency>
    <groupId>com.baeldung</groupId>
    <artifactId>exchange-rate-api</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

At this point, we can call the SPI from our application:

在这一点上,我们可以从我们的应用程序中调用SPI

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

4.4. Running the Application

4.4.运行应用程序

Let’s now focus on building all of our modules:

现在让我们专注于构建我们所有的模块。

mvn clean package

Then we run our application with the Java command without taking into account the provider:

然后我们用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

Now we’ll include our provider in java.ext.dirs extension and we run the application again:

现在我们将在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

We can see that our provider is loaded.

我们可以看到,我们的提供者已被加载。

5. Conclusion

5.结论

Now that we have explored the Java SPI mechanism through well-defined steps, it should be clear to see how to use the Java SPI to create easily extensible or replaceable modules.

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

Although our example used the Yahoo exchange rate service to show the power of plugging-in to other existing external APIs, production systems don’t need to rely on third-party APIs to create great SPI applications.

尽管我们的例子使用了雅虎的汇率服务来展示插入其他现有外部API的力量,但生产系统并不需要依赖第三方API来创建伟大的SPI应用。

The code, as usual, can be found over on Github.

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