Java 内置的 SPI 通过 java.util.ServiceLoader
类解析 classPath 和 jar 包的 META-INF/services/ 目录下的以接口全限定名命名的文件,并加载该文件中指定的接口实现类,以此完成调用
1.1 案例
对于智能家居系统,只要是相同品牌下的产品,连上 wifi 就能够通过手机 app 控制了,非常方便。虽然产品不断更新换代,型号更新层出不穷,但是同种家电在 app 上操作起来,功能一般都是一样的。就拿空调来说,我们在 app 上操作起来一般也就三个主要功能:开关,选模式,调节温度。
假设:我现在在客厅、卧室、书房安装了 3 款不同型号的空调,并把它们都接入到了我 app 中,那么之后的操作都是相同的几个按键,简单粗暴。
问题:无论是开关还是调温,都是通过 app 去调用设备的接口罢了,那么如果不同型号的空调各写各的接口,后端 app 在开发的时候光对接接口都麻烦的要死
解决方法:我先定义一套接口规范,不管你以后什么型号的空调,都按我的规范来实现接口。以后只要我能发现你的设备,那么都可以按相同的方法来调用接口
①:定义接口
新建一个 maven 项目 aircondition-standard
,定义一个接口:
public interface AirConditionService {
// 获取型号
String getType();
// 开关
void turnOnOff();
// 调节温度
void adjustTemperature(int temperature);
// 模式变更
void changeModel(int modelId);
}
用 maven 把它打成 jar 包,供后续的服务实现使用(服务提供者在项目中就可以引入这个 jar 包)
mvn clean install
有了这套规范,就保证了产品后期不管怎么更新换代,都能接入到系统来
②:服务实现
现有两个类型的空调:挂式空调(HangingType)、立式空调(VerticalType)
挂式空调: 新建一个项目 aircondition-hanging-type
,并引入上述 jar:
<dependency>
<groupId>com.zzc</groupId>
<artifactId>aircondition-standard</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
创建服务类,并实现前面定义的接口:
public class HangingTypeAirConditionService implements AirConditionService {
@Override
public String getType() {
return "HangingType";
}
@Override
public void turnOnOff() {
// TODO
System.out.println("挂式空调开关");
}
@Override
public void adjustTemperature(int temperature) {
// TODO
System.out.println("挂式空调调节温度");
}
@Override
public void changeModel(int modelId) {
// TODO
System.out.println("挂式空调更换模式");
}
}
在项目的 resources 的目录下,创建 META-INF/services
目录,然后以前面定义的接口名 com.zzc.airconditionstandard.service.AirConditionService
创建文件,并在文件中写入实现类的全限定名:
com.zzc.airconditionhangingtype.service.HangingTypeAirConditionService
如下图:
这样,一个服务方的简单实现就搞定了,用 maven 打成 jar 包,之后就可以提供给调用方使用了
同理,我们可以再创建一个立式空调的项目 aircondition-vertical-type
public class VerticalTypeAirConditionService implements AirConditionService {
@Override
public String getType() {
// TODO
return "VerticalType";
}
@Override
public void turnOnOff() {
// TODO
System.out.println("立式空调开关");
}
@Override
public void adjustTemperature(int i) {
// TODO
System.out.println("立式空调调节温度");
}
@Override
public void changeModel(int i) {
// TODO
System.out.println("立式空调更换模式");
}
}
在项目的 resources 的目录下,创建 META-INF/services
目录,然后以前面定义的接口名 com.zzc.airconditionstandard.service.AirConditionService
创建文件,并在文件中写入实现类的全限定名:
com.zzc.service.VerticalTypeAirConditionService
③:服务发现
现在两个服务提供方都实现了接口,下面关键的一步就是服务发现,这一步 Java 中的 spi 发现机制已经帮我们实现好了
创建一个新项目 aircondition-app
,引入上面打好的两个 jar
包
<dependency>
<groupId>com.zzc</groupId>
<artifactId>aircondition-hanging-type</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.zzc</groupId>
<artifactId>aircondition-vertical-type</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
按照上面的说法,虽然每个服务提供者对于接口都有不同的实现,但是作为调用者来说,它并不需要关心具体的实现类,我们要做的是通过接口来调用服务提供者实现的方法
下面,就是关键的服务发现环节,我们写一个方法,根据型号去调用对应空调的开关方法:
public class AirConditionApp {
public static void main(String[] args) {
new AirConditionApp().turnOn("VerticalType");
}
public void turnOn(String type){
ServiceLoader<AirConditionService> load = ServiceLoader.load(AirConditionService.class);
for (AirConditionService iAircondition : load) {
System.out.println("检测到:"+iAircondition.getClass().getSimpleName());
if (type.equals(iAircondition.getType())){
iAircondition.turnOnOff();
}
}
}
}
测试结果:
可以看到,测试过程中,通过定义的接口 AirConditionService
发现了两个实现类,并通过参数,调用了特定实现类的某个方法。整段代码中没有出现过具体的服务实现类,操作都是通过接口调用
1.3 JDBC 的 SPI 机制
深入理解 Java 中的 SPI 机制