今天在看dubbo源码的时候,看到大量的SPI,对于SPI不是很明白,于是网上看资料和例子,有了这篇文章。
SPI(Service Provider Interface),是Java提供的一套用来被第三方实现或者扩展的API,可以用来启动框架扩展和替换组件。
使用场景
- 数据库驱动加载
- dubbo
- 日志门面模式实现不同日志
SPI 的使用
定义接口并实现接口
public interface Spi {
/**
* spi接口
* */
void sayHello();
}
public class Cat implements Spi {
@Override
public void sayHello() {
System.out.println("Hello World! This is a Cat");
}
}
public class Dog implements Spi {
@Override
public void sayHello() {
System.out.println("Hello World! This is a Dog");
}
}
src/main/resources/ 创建文件
在src/main/resources/ 目录下创建 /META-INF/services文件(关于services文件夹,如果使用Java自带的需要使用这个名字,如果自己实现可以自定义),并在文件夹中 创建与接口同名的文件 ——com.example.spi.Spi
:
测试
public static void main(String[] args) {
ServiceLoader<Spi> services = ServiceLoader.load(Spi.class);
Iterator<Spi> iterator = services.iterator();
while (iterator.hasNext()) {
iterator.next().sayHello();
}
}
输出:
Hello World! This is a Cat
Hello World! This is a Dog
SPI 原理解析
public final class ServiceLoader<S> implements Iterable<S>{
//被加载的类或接口
private final Class<S> service;
private final String serviceName;
private final ModuleLayer layer;
// 用于定位、加载和初始化providers的加载类
private final ClassLoader loader;
// 创建ServiceLoader是用于上下文切换
private final AccessControlContext acc;
//缓存providers,按实例化的顺序排列
private Iterator<Provider<S>> lookupIterator1;
private final List<S> instantiatedProviders = new ArrayList<>();
// The lazy-lookup iterator for stream operations
private Iterator<Provider<S>> lookupIterator2;
private final List<Provider<S>> loadedProviders = new ArrayList<>();
private boolean loadedAllProviders;
}
流程
-
用ServiceLoader.load方法ServiceLoader.load方法内先创建一个新的ServiceLoader,并实例化该类中的成员变量,包括:
- loader(ClassLoader类型,类加载器)
- acc(AccessControlContext类型,访问控制器)
- providers(LinkedHashMap<String,S>类型,用于缓存加载成功的类)
- lookupIterator(实现迭代器功能)
-
应用程序通过迭代器接口获取对象实例
-
读取配置文件
-
通过反射Class.forName()加载类对象,并初始化
-
把实例化后的类缓存到providers对象中,然后返回实例对象