spi

SPI(服务提供者接口)

SPI(Service Provider Interface)是一种动态替换编码的机制(可以取代代码中大量的if-else硬编码)。通过SPI机制,不同的服务提供商可以对服务接口进行不同实现,不需要更改接口的代码,还可以在运行时进行动态的替换。


(一)Java中的实现:ServiceLoader

1.Demo

  1. 假设我有JSpiService接口和它的两个实现类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public interface JSpiService {
/**
* Print something.
*/
void print();
}

public class PiJSpiServiceImpl implements JSpiService {
/**
* Print pi.
*/
@Override
public void print() {
System.out.println(Math.PI);
}
}

public class HelloJSpiServiceImpl implements JSpiService {
/**
* Print Hello World.
*/
@Override
public void print() {
System.out.println("Hello World.");
}
}
  1. resources目录下新建META-INF.services目录,根据接口的路径创建spi.JpiService文件,在文件中写入实现类的路径:

META-INF.services

  1. 通过ServiceLoader<S> load(Class<S> service)进行服务调用:
1
2
3
4
ServiceLoad<JSpiService> services = ServiceLoader.load(JSpiService.class);
for (JSpiService service : services) {
service.print();
}

2.部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// ServiceLoader会提供一个迭代器,用于遍历所有服务实现类
public final class ServiceLoad<S> implements Iteerable<S> {

// ServiceLoader扫描的路径前缀,即/resources/META-INF/services目录
private static final String PREFIX = "META-INF/services";

private final Class<S> service;

private final ClassLoader loader;

private final AccessControlContext acc;

// 根据初始化顺序,缓存服务实现类
private LinkedHashMap<String, S> providers = new LinkedHashMap();

// 通过一个自己实现的懒加载迭代器,查找所有服务实现类
private LazyIterator lookupIterator;

// 创建新的ServiceLoader(使用当前线程的上下文类加载器)
public static <S> ServiceLoader<S> load(Class<S> serviec) {
// 使用当前线程的上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}

public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
// 调用私有化构造函数
return new ServiceLoader<>(service, loader);
}

// 构造函数
private ServiceLoader(CLass<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface connot be null");
// 如果传入的类加载器为空,使用系统类加载器
laoder = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) AccessController.getContext() : null;
reload();
}

// 清除缓存,重载所有提供类
public void reload() {
// 清空服务提供者缓存
providers.clear();
// 重新通过迭代器加载所有服务实现类
lookupInterator = new LazyIterator(service, loader);
}

// 自定义的迭代器
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() {
// ……
}

// 迭代器获取下一个服务实现类
private S nextService() {
if (!hasNextService()) throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 通过类加载器加载类,但是不实例化
// Class.forName()和ClassLoader.load()的区别是前者可以对加载的类进行初始化,但是传入false则不初始化
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 {
// 通过Class#newInstance()实例化
S p = service.cast(c.newInstance());
provisers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service, "Provider " + cn + "could not be instantiated", x);
}
throw new Error(); //This cannot happen
}
}
}

3.应用:java.sql.Driver

Java中定义了接口java.sql.Driver,但是其实现类都是交给各家厂商来提供的:

  1. mysql-connector-java.jarMETA-INF/services目录下会有名为java.sql.Driver的文件,内容是:
1
2
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
  1. postgresql.jarMETA-INF/services目录下会有名为java.sql.Driver的文件,内容是:
1
org.postgresql.Driver
  1. 如果我要使用MySQL的驱动:
1
2
String url = "jdbc:mysql://localhost:3306/test";
Connection conn = DriverManager.getConnection(url,username,password);
  1. DriverManager中通过SPI调用了驱动实现类:
1
2
3
4
5
6
7
8
9
10
11
// 通过SPI加载、实例化各实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.clas);
// 通过迭代器遍历各实现类
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try {
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// ……
}

4.存在的问题

  1. 线程不安全
  2. ServiceLoader在遍历时会通过newInstance()进行实例化,这就要求服务实现类一定要有一个无参构造函数
  3. 通过迭代器无法指定具体的服务实现类,也就是说会把所有服务实现类都加载并初始化,然后才能筛选出你需要的那个实现类,代价较大
  4. 破坏了双亲委派模型

(二)SpringBoot SPI

  1. 配置文件META-INF/spring.factories,不同于Java的地方是,SpringBoot只有一个配置文件,格式为K/V存储,其中key是接口名称,value是实现类名称,多个value以逗号分隔;不像Java每个接口需要一个配置文件。
  2. 实现类:SpringFactoriesLoader # loadFactories()/loadFactoryNames()
-------------本文结束感谢您的阅读-------------
急事可以使用右下角的DaoVoice,我绑定了微信会立即回复,否则还是推荐Valine留言喔( ఠൠఠ )ノ
0%