总会有地上的生灵,敢于直面雷霆的威光。 —— 枫原万叶 《原神》
什么是JDK SPI 机制?
SPI (Service Provider Interface) 是一种将服务接口与服务实现分离以达到解耦、大大提升了程序可扩展性的机制。引入服务提供者就是引入了spi接口的实现者,通过本地的注册发现获取到具体的实现类,轻松可插拔。
当服务的提供者提供了一种接口的实现之后,需要在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件,此文件记录了该 jar 包提供的服务接口的具体实现类。当某个应用引入了该 jar 包且需要使用该服务时,JDK SPI 机制就可以通过查找这个 jar 包的 META-INF/services/ 中的配置文件来获得具体的实现类名,进行实现类的加载和实例化,最终使用该实现类完成业务功能。
为什么要引入JDK SPI?
上述已经描述清楚,在Java中SPI是被用来设计给服务提供商做插件用的,基于策略模式,来实现动态加载机制。
例如我们在程序中只定义一个接口(规范),而具体的实现交给不同的服务提供者,在程序的启动中,读取设置好的配置文件,由配置文件决定加载哪一种服务的实现。
这种将服务接口与实现分离达到解耦的作用,同时也提升了程序的可扩展性。
举例说明:
使用Java语言访问数据库的时候我们会使用到Java.sql.Driver接口
不同数据库产品底层的协议不同,提供的Driver实现也不同,而开发者也不确定用户会选择哪种数据库,所以这种情况下可以使用JDK SPI机制,根据配置文件 用户选择哪种数据库,就调用哪种数据库的实现。
简单实现
写一个简单的例子
有一个接口Log,定义一个方法log,作用:作为日志输出传入参数。
而实现接口的方式有两种,分别是Log4j和Logback。
具体要使用哪种方式看配置文件。
就这样,一个小例子
Log接口
public interface Log {
void log(String info);
}
两个实现类
public class Logback implements Log {
@Override
public void log(String info) {
System.out.println("Logback:" + info);
}
}
public class Log4j implements Log {
@Override
public void log(String info) {
System.out.println("Log4jLog4j:" + info);
}
}
service配置文件
service.impl.Log4j
service.impl.Logback
测试类
public class Main {
public static void main(String[] args) throws IOException {
ServiceLoader<Log> spiLoader = ServiceLoader.load(Log.class);
Iterator<Log> iteratorSpi = spiLoader.iterator();
while (iteratorSpi.hasNext()) {
Log log = iteratorSpi.next();
log.log("MRyan");
}
}
}
源码分析
由 ServiceLoader的load方法作为入口进行分析 传入的参数是接口类
public static <S> ServiceLoader<S> load(Class<S> service) {
//获取当前线程绑定的ClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
接着调用重载load()传入接口类和,当前线程绑定的ClassLoader
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader){
//为给定的服务类型和类加载器创建一个新的服务加载器。
return new ServiceLoader<>(service, loader);
}
// 表示正在加载的服务的类或接口
private final Class<S> service;
// 用于查找,加载和实例化提供程序的类加载器
private final ClassLoader loader;
// 创建ServiceLoader时采取的访问控制上下文
private final AccessControlContext acc;
private ServiceLoader(Class<S> svc, ClassLoader cl) {
//判断当前接口类引用返回是否为空
service = Objects.requireNonNull(svc, "Service interface cannot be null");
//判断当前线程绑定的ClassLoader是否为空 为空则调用SystemClassLoader
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
//判断AccessControlContext是否为空
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
//清除此加载程序的提供程序缓存,重新加载所有提供程序
reload();
}
核心方法reload,来看看它都做了什么
// provider的缓存
//该缓存用来记录 ServiceLoader 创建的实现对象,其中 Key 为实现类的完整类名,Value 为实现类的对象。
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
//懒加载迭代器
private LazyIterator lookupIterator;
//清除此加载程序的提供程序缓存,重新加载所有提供程序
public void reload() {
//清空provider的缓存
providers.clear();
//创建 LazyIterator 迭代器,用于读取 SPI 配置文件并实例化实现类对象。
lookupIterator = new LazyIterator(service, loader);
}
有点好奇LazyIterator是什么,看名字像是迭代器,有点眼熟,我们发现测试类中迭代器就是调用 ServiceLoader.LazyIterator 实现的。
Iterator 接口有两个关键方法:hasNext() 方法和 next() 方法。
这里的 LazyIterator 中的next() 方法最终调用的是其 nextService() 方法,
hasNext() 方法最终调用的是 hasNextService() 方法
private static final String PREFIX = "META-INF/services/";
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private class LazyIterator
implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
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
}
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
其中着重看下hasNextService方法
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//很熟悉有没有 PREFIX 赋值是 "META-INF/services/"
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);
}
}
// 按行SPI遍历配置文件的内容
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 解析配置文件
pending = parse(service, configs.nextElement());
}
//更新字段
nextName = pending.next();
return true;
}
在 hasNextService() 方法中完成 SPI 配置文件的解析之后,再来看nextService() 方法,该方法负责实例化 hasNextService() 方法读取到的实现类,其中会将实例化的对象放到 providers 集合中缓存起来,核心实现如下所示:
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// // 加载 nextName字段指定的类
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
}
说了这么多迭代器的实现,那么测试类中的迭代是怎么实现的?
public Iterator<S> iterator() {
return new Iterator<S>() {
// knownProviders用来迭代providers缓存
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
// 先走查询缓存,缓存查询失败,再通过LazyIterator加载
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
//// 先走查询缓存,缓存查询失败,再通过 LazyIterator加载
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
到这里,文章就接近尾声了,了解了SPI是什么,SPI能做什么,SPI的实现,那么问题来了,他的缺点是什么呢?
其实很明显真正实现是需要读取配置类的信息,那我们如果需要新增一个驱动类的话,是不是需要在配置类中手动的添加一行,那删除一个驱动类的时候也需要手动删除,那这样的话扩展性是不是就不好了呢。