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实现的完全限定名称。
S服务提供者以扩展的形式安装,一个jar文件,我们将其放在应用程序类路径、Java扩展类路径或用户定义的类路径中。
2.4 服务加载器
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. 演示:货币汇率应用程序
现在我们了解了基础知识,让我们描述设置汇率应用程序所需的步骤。
为了突出显示这些步骤,我们至少需要使用3个项目: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小节中将所有内容整合在一起。
事实上,我们可以为服务提供者提供任意数量的模块,并使它们在模块exchange-rate-app的类路径中可用。
4.1 构建我们的API
我们首先创建一个名为exchange-rate-api的Maven项目。名称以术语api结尾是一种很好的做法,但我们可以随意命名它。
然后我们创建一个模型类来表示汇率货币:
package cn.tuyucheng.taketoday.rate.api;
public class Quote {
private String currency;
private LocalDate date;
// ...
}
然后我们通过创建接口QuoteManager来定义用于检索报价的服务:
package cn.tuyucheng.taketoday.rate.api;
public interface QuoteManager {
List<Quote> getQuotes(String baseCurrency, LocalDate date);
}
接下来,为我们的服务创建一个SPI:
package cn.tuyucheng.taketoday.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>cn.tuyucheng.taketoday</groupId>
<artifactId>exchange-rate-api</artifactId>
<version>1.0.0</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/cn.tuyucheng.taketoday.rate.spi.ExchangeRateProvider
该文件的内容是SPI实现的完全限定类名:
cn.tuyucheng.taketoday.rate.impl.YahooFinanceExchangeRateProvider
4.3 整合
最后,让我们创建一个名为exchange-rate-app的客户端项目,并将依赖项exchange-rate-api添加到类路径中:
<dependency>
<groupId>cn.tuyucheng.taketoday</groupId>
<artifactId>exchange-rate-api</artifactId>
<version>1.0.0</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.jar:./exchange-rate-app/target/exchange-rate-app-1.0.0.jar cn.tuyucheng.taketoday.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.jar:./exchange-rate-app/target/exchange-rate-app-1.0.0.jar cn.tuyucheng.taketoday.rate.app.MainApp
我们可以看到我们的提供者已加载。
5. 总结
现在我们已经通过明确定义的步骤探索了Java SPI机制,应该清楚地了解如何使用Java SPI创建易于扩展或可替换的模块。
尽管我们的示例使用Yahoo汇率服务来展示插入其他现有外部API的强大功能,但生产系统不需要依赖第三方API来创建出色的SPI应用程序。
与往常一样,本教程的完整源代码可在GitHub上获得。