0%

Java SPI 机制

今天在看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 {

/**
* 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;

// 用于定位、加载和初始化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对象中,然后返回实例对象

源码

客官,赏一杯coffee嘛~~~~