SPI 全称为 (Service Provider Interface) ,是JVM内置的一种服务提供发现机制。Java在语言层面为我们提供了一种方便地创建可扩展应用的途径。我们只需要按照SPI的要求,在jar包中进行适当的配置,jvm就会在运行时通过懒加载,帮我们找到所需的服务并加载。如果我们一直不使用某个服务,那么它不会被加载,一定程度上避免了资源的浪费。
熟悉JDBC的同学都知道,在jdbc4.0之前,在使用DriverManager获取DB连接之前,我们总是需要通过Class.forName显示的加载驱动(为了执行驱动类的static代码,注册驱动实例对象到DriverManager中),例如:
Connection comm = null;
Statement stmt = null;
try {
//注册mysql的jdbc驱动
Class.forName("com.mysql.jdbc.Driver");
//创建连接
conn = DriverManagger.getConnection(url,user,pwd);
}
在JDBC4.0开始,这个显式的初始化不再是必选项了,它存在的意义只是为了向上兼容。那么JDBC4.0之后,我们的应用是如何找到对应的驱动呢?答案就是SPI。
完成以上后,在程序运行时可使用ServiceLoader.load(Class class) api获取到接口服务的所有实现类。SPI底层原理:使用懒加载的方式,在运行时将该服务接口的实现类通过Class.forName的方式加载到JVM中,并做实例初始化。
1)接口服务:
package spi;
public interface Animal {
void eat();
void sleep();
}
2)服务实现:
package spi;
public class Dog implements Animal{
@Override
public void eat() {
System.out.println("dog eat...");
}
@Override
public void sleep() {
System.out.println("dog sleep...");
}
}
3)SPI配置文件:
在resource下创建META-INf/services目录,然后创建文件spi.Animal,内容:spi.Dog
4)测试代码:
package spi;
import java.util.Iterator;
import java.util.ServiceLoader;
public class SPITest {
public static void main(String[] args) {
ServiceLoader animals = ServiceLoader.load(Animal.class);
for (Animal animal : animals) {//增强型for循环等价于下面的iterator迭代
animal.eat();
animal.sleep();
}
Iterator iterator = animals.iterator();
while(iterator.hasNext()) {
Animal animal = iterator.next();
animal.eat();
animal.sleep();
}
}
}
说明:本示例是在一个工程下演示的,可以将Animal接口打包成animal.jar,Dog实现工程中实现引入animal.jar,并且配置META-INF文件,在打包成dog.jar。最后创建一个test工程,引入dog.jar即可进行测试。
public final class ServiceLoader implements Iterable {
//...
//重新load指定serivice的实现。通过LazyIterator实现懒加载。
public void reload() {
providers.clear();//是个LinkedHashMap 类型
lookupIterator = new LazyIterator(service, loader);
}
//私有ServiceLoader构造函数,须通过ServiceLoader.load(Class>)静态方法创建ServiceLoader实例
private ServiceLoader(Class 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();
}
//构建ServiceLoader实例
public static ServiceLoader load(Class service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
//通过service的class创建ServiceLoader实例,默认使用上下文classloader
public static ServiceLoader load(Class service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
}
说明:
ServiceLoader实现Iterable>接口,调用load方法(创建ServiceLoader实例)返回了一个服务类型的迭代器,接下来使用iterator(或者增强型for循环)遍历ServiceLoader实例时,才会真正的扫描、加载对应的服务实现类。
public final class ServiceLoader implements Iterable {
//...
//缓存的service provider,按照初始化顺序排列。
private LinkedHashMap providers = new LinkedHashMap<>();
//当前的LazyIterator迭代器指针,服务懒加载迭代器
private LazyIterator lookupIterator;
//增强型for循环或者直接iterator()方法遍历ServiceLoader
public Iterator iterator() {
return new Iterator() {
//创建Iterator迭代器时的ServiceLoader.providers快照,
//因此在首次迭代时,iterator总是会通过LazyIterator进行懒加载
Iterator> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
// 如果已经扫描过,则对providers进行迭代;
if (knownProviders.hasNext())
return true;
// 如果没有扫描过,则通过lookupIterator进行扫描和懒加载
return lookupIterator.hasNext();
}
public S next() {
// 如果已经扫描过,则对providers进行迭代;
if (knownProviders.hasNext())
return knownProviders.next().getValue();
// 如果没有扫描过,则通过lookupIterator进行扫描和懒加载
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
说明:
public final class ServiceLoader implements Iterable {
private static final String PREFIX = "META-INF/services/";
//...
private class LazyIterator implements Iterator {//内部类
Class service;
ClassLoader loader;
Enumeration configs = null;
Iterator pending = null;//配置文件中的服务实现类的迭代器(每行一个)
String nextName = null;
private LazyIterator(Class 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)//通过ClassLoader.getResources()获得资源URL集合
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;
try {
Class> c = Class.forName(cn, false, loader);
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (ClassNotFoundException x) {
fail(service,"Provider " + cn + " not found");
}
throw new Error(); // This cannot happen
}
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
}
说明:
ServiceLoader类并不复杂,实现了SPI的所有逻辑,内部出了上面说的一些方法外,还有parse及parseLine的代码,可以发现,parseLine中会对服务实现类进行去重,所以在一个或多个services配置文件中配置多次的服务实现类只会被处理一次。
在https://blog.csdn.net/liuxiao723846/article/details/112397128 已经介绍过一次jdbc驱动的加载过程,这里再简单重复一下。
DriverManager的static代码块中会执行loadInitialDrivers方法,该方法内部首先通过jdbc.drivers配置加载驱动,然后会通过SPI来加载驱动,如下:
在mysql驱动库中实现了SPI标准,并且在static代码块中创建驱动实例,注册到jdbc的DriverManager中。接下来,就可以通过DriverManager来创建connection了。
参考:
https://www.jianshu.com/p/27c837293aeb
https://linxiaobai.github.io/2018/05/18/ClassLoader%E5%8E%9F%E7%90%86%E4%BB%A5%E5%8F%8ASPI/