Java SPI 服务发现机制

SPI 全称是 Service Provider Interface,是java提供的一个服务发现提供机制,可以用来动态的启用和替换框架。在很多的开源框架中都使用了该机制,Dubbo中就大量的使用了该机制。

使用场景

我的理解是,它比较像java的多态,有一个定义好接口,有多个实现,根据需要,我们可以加载不同的实现类。比较常见的就是jdbc,java定义好所有的接口,各个数据库厂商实现自己的驱动

使用

我们以加载一个主题为例,定义一个主题接口,实现黑色和白色两个主题

定义好接口

1
2
3
4
5
6
7
package com.smile;

public interface Theme {

void load();

}

定义多个实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.smile.impl;

import com.smile.Theme;

public class DrakTheme implements Theme {

public void load() {
System.out.println("我是黑色主题");
}
}

package com.smile.impl;

import com.smile.Theme;

public class WhiteTheme implements Theme {

public void load() {
System.out.println("我是白色主题");
}
}

配置文件

在项目中的resources文件夹下创建META-INF/services的两级目录,并添加配置主题接口的配置文件,文件的名字必须跟接口的全类限定名一致,也就是com.smile.Theme.同时,文件的内容是两个实现类的全类限定名。如下图所示:

配置文件

测试

接下来我们对两个实现类进行测试,我们需要使用ServiceLoader来加载我们的实现类。直接看代码实现:

1
2
3
4
5
6
7
public class ThemeTest {

public static void main(String[] args) {
ServiceLoader<Theme> loader = ServiceLoader.load(Theme.class);
loader.forEach(t -> t.load());
}
}

控制台打印:

1
2
3
4
我是黑色主题
我是白色主题

Process finished with exit code 0

可以看到,程序成功的加载了主题的实现类,并成功执行。

原理

该机制的原理就是查找配置文件并使用反射进行动态的加载类文件.

通过查看源码,我们可以发现,load方法只是设置了信息,在进行遍历实例元素时才会进行查找并进行实例化的操作。部分代码如下所示:

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
public S next() {
if (!this.hasNext()) {
throw new NoSuchElementException();
} else {
String var1 = this.nextName;
this.nextName = null;
Class var2 = null;

try {
var2 = Class.forName(var1, false, this.loader);
} catch (ClassNotFoundException var5) {
ServiceLoader.fail(this.service, "Provider " + var1 + " not found");
}

if (!this.service.isAssignableFrom(var2)) {
ServiceLoader.fail(this.service, "Provider " + var1 + " not a subtype");
}

try {
//实例化对象
Object var3 = this.service.cast(var2.newInstance());
ServiceLoader.this.providers.put(var1, var3);
return var3;
} catch (Throwable var4) {
ServiceLoader.fail(this.service, "Provider " + var1 + " could not be instantiated: " + var4, var4);
throw new Error();
}
}
}

参考

Dubbo SPI