什么是SPI?
SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制。有一个接口,有多个实现,通过配置来获取接口的实现(服务发现)
Java SPI 应用实例
当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务实现的工具类是:java.util.ServiceLoader。
示例
SPI接口
package com.zmx.study.eg9;
/**
* 一个SPI接口
*/
public interface PayService {
void pay();
}
SPI具体实现
第一个实现
public class AliPayService implements PayService {
@Override
public void pay() {
System.out.println("AliPay...");
}
}
第二个实现
public class WxPayService implements PayService {
@Override
public void pay() {
System.out.println("WxPay...");
}
}
增加META-INF目录文件
1、Resource下面创建META-INF/services 目录里创建一个以服务接口命名的文件
配置.png文件名必须是SPI接口的全名称(包名.类名)
2、在com.zmx.study.eg9.PayService文件中写入配置信息
com.zmx.study.eg9.AliPayService
com.zmx.study.eg9.WxPayService
测试
通过ServiceLoader获取服务
public static void main(String[] args) {
List<PayService> payServiceList = new ArrayList<>();
ServiceLoader<PayService> serviceLoader = ServiceLoader.load(PayService.class);
serviceLoader.forEach((service) -> {
payServiceList.add(service);
service.pay();
});
System.out.println("发现PayService服务个数:" + payServiceList.size());
}
实现机制:
ServiceLoader.load最后走到了reload方法,LazyIterator实现了Iterator接口。
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
//判断有没有下一个服务
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// PREFIX就是一个常量:META-INF/services/
// service.getName()就是接口的完整类名
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
// ...
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 根据配置信息接口实现的类名获取Class
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
// 创建实例
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
网友评论