什么是SPI
被广大程序员所熟知的就是API,API叫做"Application Programming Interface",即应用程序接口,是框架对外提供的能力的接口。SPI叫做"Service Provider Interface",即服务提供接口,是用于扩展框架能力的。
SPI有什么用
我们长听说面向接口编程,那么面向接口编程的有点是什么呢?面向接口编程的优点是不需要关注实现逻辑和细节,完全可插拔。
JAVA中SPI实现分析
下图是SPI使用的一个流程:
从图中可以看出最重要的是实现加载服务实现。JAVA SPI接口的实现实际上是"接口+策略模式+配置文件"
JAVA SPI实现要求以及服务加载:
- 当服务提供者提供了接口的一种具体实现后,在jar包的META-INF/services目录下创建一个以接口全限定名为命名的文件,内容为实现类的全限定名,每行一个实现。具体做法就是在resources下创建META-INF/services目录
- 接口实现类所在的jar包放在主程序的classpath中;
- 主程序通过java.util.ServiceLoder动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM。main函数中调用ServiceLoder加载接口类。
- SPI的实现类必须携带一个不带参数的构造方法
ServiceLoder.java加载实现机制:
//指定目录加载的资源
private static final String PREFIX = "META-INF/services/";
// 需要被加载的接口
private final Class<S> service;
// 类加载器
private final ClassLoader loader;
// 访问控制
private final AccessControlContext acc;
//缓存providers,按实例化的顺序排列
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 懒查找迭代器
private LazyIterator lookupIterator;
//这里会发现一个问题:如果想要热加载某个类的实现的话,会造成全局的重新扫描,比较浪费资源
public void reload() {
//每次调用都会清除掉之前加载的实现
providers.clear();
//重新加载loader以及实现
lookupIterator = new LazyIterator(service, loader);
}
// 调用load接口
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
//获取配置文件中的信息
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
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);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//实例化接口实现
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
}
SPI的优缺点
优点:
模块解耦,系统扩展性增强
缺点:
使用的时候,会重新加载所有实现类,资源浪费。