什么是SPI?

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」。

之前参加华为面试时,面试官问了一个问题,你了解过SPI吗,当时是一脸懵逼。过了很久也没有深入去了解这究竟是一个什么东西。最近有时间就深入研究一下。

概述

服务提供者接口 (SPI),是Java 6引入的一项功能,用于发现加载与给定接口匹配的实现。Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。SPI的核心思想就是解耦

SPI术语和定义

服务

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

服务提供者接口

充当代理或服务端点的接口或抽象类。如果服务是一个接口,则它与服务提供者接口相同。

服务提供者

SPI 的具体实现是服务提供程序包含一个或多个实现或扩展服务类型的具体类。服务提供者是通过我们放在资源目录META-INF /services中的提供商配置文件进行配置和识别的。文件名是 SPI 的完全限定名,其内容是 SPI 实现的完全限定名。

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

服务加载器

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

使用场景

调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略

Java中的SPI示例

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

使用介绍

要使用Java SPI,需要遵循如下约定:

  • 1、当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名;
  • 2、接口实现类所在的jar包放在主程序的classpath中;
  • 3、主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM;
  • 4、SPI的实现类必须携带一个不带参数的构造方法;

    实例应用:货币汇率应用程序

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

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

构建 API

首先创建一个名为exchange-rate-api的 Maven 项目,然后我们创建一个模型类来表示汇率货币:

package com.tudo.rate.api; ​ public class Quote {    private String currency;    private LocalDate date;   ... }

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

package com.tudo.rate.api ​ public interface QuoteManager {    List<Quote> getQuotes(String baseCurrency, LocalDate date); }

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

package com.tudo.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 loader = ServiceLoader     .load(ExchangeRateProvider.class);

   public Iterator providers(boolean refresh) {        if (refresh) {            loader.reload();       }        return loader.iterator();   } } ```

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

构建服务提供者

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

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

该文件的内容是 SPI 实现的全限定类名:

com.tudo.rate.impl.YahooFinanceExchangeRateProvider

集成

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

<dependency>    <groupId>com.baeldung</groupId>    <artifactId>exchange-rate-api</artifactId>    <version>1.0.0-SNAPSHOT</version> </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.tudo.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.tudo.rate.app.MainApp

可以看到我们的提供程序已加载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值