就是服务的调用方定义一个接口规范,可以由不同的服务提供者实现。并且,调用方能够通过某种机制来发现服务提供方,并通过接口调用它的能力
API 中的接口是服务提供者给服务调用者的一个功能列表,而 SPI 中更多强调的是,服务调用者对服务实现的一种约束,服务提供者根据这种约束实现的服务,可以被服务调用者发现。
说白了,Java 中的 SPI 实现的就是,你按我的接口规范实现服务,我就能通过某种机制为这个接口寻找到这个服务。
实现
java spi的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件,该文件里就是实现该服务接口的具体实现类
而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定jdk提供服务实现查找的一个工具类:java.util.ServiceLoader
具体代码
这里根据具体业务逻辑具体实现
a.没有找到具体实现抛出异常 b.如果发现多个实现,分别打印
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 的工作流程,我们再来看看它的实现,其实最关键的就是上面代码中出现的ServiceLoader这个类。上面的示例代码中,对于ServiceLoader的load()方法的结果,我们用for循环进行了遍历,这一点我们看一下源码就能明白,因为ServiceLoader实现了Iterable这一接口,而整个服务发现的核心,就在它的iterator()方法中。
注意这里面有两个关键的东西,找一下在源码中定义的地方:
注释写的非常明白,providers就是一个缓存,在迭代器中如果先从这里面进行查找,如果里面有就继续往下找,没有了的话就用这个懒加载的lookupIterator查找。
在nextService()方法中,则会先加载这个实现类,然后实例化对象,最终放入缓存中去。
在迭代器的迭代过程中,会完成所有实现类的实例化,其实归根结底,还是基于 java 反射去实现的。
要说 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 包看一下具体结构:
Java 中的 SPI 提供了一种比较特别的服务发现和调用机制,通过接口灵活的将服务调用与服务提供者分离,用于提供给第三方实现扩展时还是很方便的。但是也有缺点,比方说一旦加载一个接口,就会把所有实现类都加载进来,可能会加载到不需要的冗余服务。不过站在整体角度上,还是给我们提供了一种非常不错的框架扩展、集成的思路。