目录
  1. 1. Java–深入理解SPI机制
    1. 1.1. 简介
    2. 1.2. 例子
    3. 1.3. 测试
    4. 1.4. 源码分析
    5. 1.5. 不足
    6. 1.6. 总结
Java--深入理解SPI机制

Java–深入理解SPI机制

总结于《深入理解 Java 中 SPI 机制》《深入理解SPI机制》

简介

  SPI(Service Provider Interface),是 JDK 内置的一种 服务发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如 java.sql.Driver 接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL 和PostgreSQL 都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中 SPI 机制主要思想是将装配的控制权移到程序之外(有点 IOC 内味了),在模块化设计中这个机制尤其重要,其核心思想就是 解耦

1568077097364725

SPI与API区别:

  • API是调用并用于实现目标的类、接口、方法等的描述;
  • SPI是扩展和实现以实现目标的类、接口、方法等的描述;

换句话说,API 为操作提供特定的类、方法,SPI 通过操作来符合特定的类、方法。

例子

  首先我们通过一个小例子,能更好的理解 SPI 机制:

  • 首先我们自定义一个接口,SPIService
1
2
3
public interface SPIService {
void excute();
}
  • 然后我们写两个实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
public class SpiImpl1 implements SPIService{
@Override
public void excute() {
System.out.println("SpiImpl1.excute()");
}
}
//只是单纯的两个实现类,之间做一下区分
public class SpiImpl2 implements SPIService{
@Override
public void excute() {
System.out.println("SpiImpl2.excute()");
}
}
  • 最后要在 classpath 路径下配置添加一个文件,文件名是 SPIService 接口的全限定名,内容是实现了这个接口的类的全限定名,多个实现类之间用回车隔开。

image-20200301154217277

文件里的内容:

image-20200301154308588

测试

  然后我们用 SPI 提供的类 ServiceLoader.load或者Service.providers方法来拿到他们的实现类的实例。其中,Service.providers包位于sun.misc.Service,而ServiceLoader.load包位于java.util.ServiceLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {

public static void main(String[] args) {
Iterator<SPIService> providers = Service.providers(SPIService.class);
ServiceLoader<SPIService> loader = ServiceLoader.load(SPIService.class);
//两种不同的方式拿到实现类的实例
while (providers.hasNext()){
SPIService spi = providers.next();
spi.excute();
}
System.out.println("----------------------------------");
Iterator<SPIService> iterator = loader.iterator();
while (iterator.hasNext()){
SPIService spi = iterator.next();
spi.excute();
}
}
}
  • 运行结果:
1
2
3
4
5
SpiImpl1.excute()
SpiImpl2.excute()
----------------------------------
SpiImpl1.excute()
SpiImpl2.excute()

源码分析

  • 我们可以看到,这里规定了默认的配置文件路径 META-INF/services
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ServiceLoader实现了Iterable接口,可以遍历所有的服务实现者
public final class ServiceLoader<S> implements Iterable<S>
{
// 查找配置文件的目录
private static final String PREFIX = "META-INF/services/";
// 表示要被加载的服务的类或接口
private final Class<S> service;
// 这个ClassLoader用来定位,加载,实例化服务提供者
private final ClassLoader loader;
// 访问控制上下文
private final AccessControlContext acc;
// 缓存已经被实例化的服务提供者,按照实例化的顺序存储
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 迭代器
private LazyIterator lookupIterator;
}
  • 核心功能的实现即查找实现类和创建类实例的过程都是由 LazyIterator 实现的
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
// 服务提供者查找的迭代器
private class LazyIterator implements Iterator<S> {
// 服务提供者接口
Class<S> service;
// 类加载器
ClassLoader loader;
// 保存实现类的url
Enumeration<URL> configs = null;
// 保存实现类的全名
Iterator<String> pending = null;
// 迭代器中下一个实现类的全名
String nextName = null;

public boolean hasNext() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
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;
}

public S next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
//遍历迭代器中的类挨个实例化
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//通过反射创建实现类的实例
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 {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service, "Provider " + cn + " could not be instantiated: " + x, x);
}
throw new Error(); // This cannot happen
}
}

首先,**ServiceLoader实现了Iterable接口,所以它有迭代器的属性,这里主要都是实现了迭代器的hasNext和next方法。这里主要都是调用的lookupIterator的相应hasNext和next方法,lookupIterator是懒加载迭代器。

其次,LazyIterator中的hasNext方法,静态变量PREFIX就是”META-INF/services/”目录,这也就是为什么需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件。

最后,通过反射方法Class.forName()加载类对象,并用newInstance方法将类实例化,并把实例化后的类缓存到providers对象中,(LinkedHashMap<String,S>类型) 然后返回实例对象。

不足

  1. 不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。

  2. 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。

  3. 多个并发多线程使用 ServiceLoader 类的实例是不安全的。

以上不足可以参考 dubbo 实现的 SPI 机制,但是我还没学

总结

  通过 SPI 服务发现机制,我们就可以实现很多复杂的场景,比如 JDBC 的场景,由 Java 提供数据库连接规定的接口然后由第三方去实现,不同的厂商有不同的实现规范。在 JDBC 的 jar 包中就有 META-IFN\services下的一个 java.sql.Driver 文件,里面的内容就是 com.mysql.cj.jdbc.Driver ,这个类就是关于数据库连接的具体实现。

文章作者: Archiver
文章链接: https://www.kaiming66.com/2020/02/23/Java/Java--%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3SPI%E6%9C%BA%E5%88%B6/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Archiver`s Blog
打赏
  • 微信
  • 支付寶

评论