今天在看dubbo源码的时候,看到大量的SPI,对于SPI不是很明白,于是网上看资料和例子,有了这篇文章。
SPI(Service Provider Interface),是Java提供的一套用来被第三方实现或者扩展的API,可以用来启动框架扩展和替换组件。
使用场景
- 数据库驱动加载
- dubbo
- 日志门面模式实现不同日志
SPI 的使用
定义接口并实现接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public interface 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
:
测试
1 2 3 4 5 6 7 8
| public static void main(String[] args) { ServiceLoader<Spi> services = ServiceLoader.load(Spi.class); Iterator<Spi> iterator = services.iterator(); while (iterator.hasNext()) { iterator.next().sayHello(); } }
|
输出:
1 2
| Hello World! This is a Cat Hello World! This is a Dog
|
SPI 原理解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public final class ServiceLoader<S> implements Iterable<S>{ private final Class<S> service; private final String serviceName;
private final ModuleLayer layer;
private final ClassLoader loader;
private final AccessControlContext acc;
private Iterator<Provider<S>> lookupIterator1; private final List<S> instantiatedProviders = new ArrayList<>();
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对象中,然后返回实例对象
源码