SPI扩展机制

JavaSPI允许服务调用方定义接口,由不同的服务提供者实现。服务提供者在jar的META-INF/services中创建接口文件,调用方通过ServiceLoader发现和加载服务。文章介绍了SPI在Spring自动装配、JDBC驱动加载、Dubbo过滤器实现以及SLF4J日志框架中的应用,并讨论了其优缺点,如灵活性与潜在的冗余服务加载问题。
摘要由CSDN通过智能技术生成

java 本身自带的 SPI 扩展机制

就是服务的调用方定义一个接口规范,可以由不同的服务提供者实现。并且,调用方能够通过某种机制来发现服务提供方,并通过接口调用它的能力

在这里插入图片描述

基于spi机制的实现,应用场景

  1. spring自动装配 spring的spi机制,实现不同配置的装载,实现filter,linster的加载;Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等
  2. JDBC加载不同类型的驱动 ,基于spi 机制实现不同驱动的加载,
  3. Dubbo的spi 机制实现过滤器的实现,Dubbo中也大量使用SPI的方式实现框架的扩展, 不过它对java提供的原生SPI做了封装
  4. SLF4J对log4j/logback的支持

SPI 接口机制和API接口的区别

API 中的接口是服务提供者给服务调用者的一个功能列表,而 SPI 中更多强调的是,服务调用者对服务实现的一种约束,服务提供者根据这种约束实现的服务,可以被服务调用者发现。
说白了,Java 中的 SPI 实现的就是,你按我的接口规范实现服务,我就能通过某种机制为这个接口寻找到这个服务。
实现

SPI具体约定

java spi的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件,该文件里就是实现该服务接口的具体实现类

而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定jdk提供服务实现查找的一个工具类:java.util.ServiceLoader

具体实现步骤测试案例

目录结构
在这里插入图片描述

  • spi-interface: 是针对厂商定义的接口项目,只提供接口,不提供实现
  • spi-boy/spi-gril: 分别是两个厂商对interface的不同实现,所以他们会依赖于interface项目
  • spi-core: 是提供给用户使用的核心jar文件, 同样依赖于interface项目, 用户使用时需要引入- - spi-core.jar和厂商具体实现的jar
  • spi-test:用来模拟用户测试, 依赖spi-core和spi-boy/spi-gril(至少一个实现,否则会报错)

具体代码

  • spi-interface
    在这里插入图片描述
  • spi-boy
    在这里插入图片描述
    在这里插入图片描述
  • spi-gril
    在这里插入图片描述
    在这里插入图片描述
  • spi-core

这里根据具体业务逻辑具体实现

a.没有找到具体实现抛出异常 b.如果发现多个实现,分别打印

在这里插入图片描述

  • spi-test
    a. 无厂商实现jar引入
    在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
b. 引入spi-boy

在这里插入图片描述
在这里插入图片描述
c. 引入spi-gril
在这里插入图片描述
在这里插入图片描述
d. 同时引入spi-gril/spi-boy
在这里插入图片描述
在这里插入图片描述

使用场景举例

说起智能家居系统,大家现在都比较熟悉了,只要是相同品牌下的产品,连上 wifi 就能够通过手机 app 控制了,非常方便。

虽然产品不断更新换代,型号更新层出不穷,但是同种家电在 app 上操作起来,功能一般都是一样的。就拿空调来说,我们在 app 上操作起来一般也就三个主要功能:开关,选模式,调节温度。

假设我现在在客厅、卧室、书房安装了 3 款不同型号的空调,并把它们都接入到了我 app 中,那么之后的操作都是相同的几个按键,简单粗暴。

在这里插入图片描述
考一下,无论是开关还是调温,都是通过 app 去调用设备的接口罢了,那么如果不同型号的空调各写各的接口,后端 app 在开发的时候光对接接口都麻烦的要死。

解决方法也很简单,我先定义一套接口规范,不管你以后什么型号的空调,都按我的规范来实现接口。以后只要我能发现你的设备,那么都可以按相同的方法来调用接口。

那么下面就先来定义这么一套接口规范,如果你以后想要接入智能家居系统,那么就要遵循这个规范来开发接口。

新建一个项目作为标准,就叫aircondition-standard好了,然后创建一个接口。除了 3 个操作以外,我们再添加一个获取空调型号的方法。

public interface IAircondition {
    // 获取型号
    String getType();

    // 开关
    void turnOnOff();

    // 调节温度
    void adjustTemperature(int temperature);

    // 模式变更
    void changeModel(int modelId);
}

这个接口后面要给服务的实现方来使用,用 maven 把它打成 jar 包,对对外发布出去;之后服务提供者在项目中就可以引入这个 jar 包了;

接口实现方基于spi 规范,在jar包的META-INF/services/ 定义接口文件,继承接口实现;有了这套规范,就保证了产品后期不管怎么更新换代,都能接入到系统来。

SPI 实现原理

spi 的工作流程,我们再来看看它的实现,其实最关键的就是上面代码中出现的ServiceLoader这个类。上面的示例代码中,对于ServiceLoader的load()方法的结果,我们用for循环进行了遍历,这一点我们看一下源码就能明白,因为ServiceLoader实现了Iterable这一接口,而整个服务发现的核心,就在它的iterator()方法中。
在这里插入图片描述

注意这里面有两个关键的东西,找一下在源码中定义的地方:

在这里插入图片描述
注释写的非常明白,providers就是一个缓存,在迭代器中如果先从这里面进行查找,如果里面有就继续往下找,没有了的话就用这个懒加载的lookupIterator查找。

在nextService()方法中,则会先加载这个实现类,然后实例化对象,最终放入缓存中去。
在这里插入图片描述

在迭代器的迭代过程中,会完成所有实现类的实例化,其实归根结底,还是基于 java 反射去实现的

slf4j基于spi机制的实现

要说 spi 的实际应用,大家最常见的应该就是日志框架slf4j了,它利用 spi 实现了插槽式接入其他具体的日志框架。

说白了,slf4j本身就是个日志门面,并不提供具体的实现,需要绑定其他具体实现才能真正的引入日志功能。

例如我们可使用log4j2作为具体的绑定器,只需要在 pom 中引入slf4j-log4j12,就可以使用具体功能。

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.3</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>2.0.3</version>
</dependency>

引入项目后,点开它的 jar 包看一下具体结构:

在这里插入图片描述

spi机制优缺点

Java 中的 SPI 提供了一种比较特别的服务发现和调用机制,通过接口灵活的将服务调用与服务提供者分离,用于提供给第三方实现扩展时还是很方便的。但是也有缺点,比方说一旦加载一个接口,就会把所有实现类都加载进来,可能会加载到不需要的冗余服务。不过站在整体角度上,还是给我们提供了一种非常不错的框架扩展、集成的思路。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值