Dubbo SPI 的工作原理

SPI 作为一种服务发现机制,允许程序在运行时动态地加载具体实现类。因其强大的可拓展性,SPI 被广泛应用于各类技术框架中,例如 JDBC 驱动、Spring 和 Dubbo 等等。Dubbo 并未使用原生的 Java SPI,而是重新实现了一套更加强大的 Dubbo SPI。本文将简单介绍 SPI 的设计理念,通过示例带你体会 SPI 的作用,通过 Dubbo 获取拓展的流程图和源码分析带你理解 Dubbo SPI 的工作原理。深入了解 Dubbo SPI,你将能更好地利用这一机制为你的程序提供灵活的拓展功能。

SPI 简介

SPI 的全称是 Service Provider Interface,是一种服务发现机制。一般情况下,一项服务的接口和具体实现,都是服务提供者编写的。在 SPI 机制中,一项服务的接口是服务使用者编写的,不同的服务提供者编写不同的具体实现。在程序运行时,服务加载器动态地为接口加载具体实现类。因为 SPI 具备“动态加载”的特性,我们很容易通过它为程序提供拓展功能。以 Java 的 JDBC 驱动为例,JDK 提供了 java.sql.Driver 接口,各个数据库厂商,例如 MySQL、Oracle 提供具体的实现。

目前 SPI 的实现方式大多是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类:

  • Java SPI:META-INF/services/full.qualified.interface.name
  • Dubbo SPI:META-INF/dubbo/full.qualified.interface.name(还有其他目录可供选择)
  • Spring SPI: META-INF/spring.factories

SPI 示例

Java SPI 示例

定义一个接口 Animal。

1
2
3
public interface Animal {
void bark();
}

定义两个实现类 Dog 和 Cat。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Dog implements Animal {
@Override
public void bark() {
System.out.println("Dog bark...");
}
}

public class Cat implements Animal {
@Override
public void bark() {
System.out.println("Cat bark...");
}
}

在 META-INF/services 文件夹下创建一个文件,名称为 Animal 的全限定名 com.moralok.dubbo.spi.test.Animal,文件内容为实现类的全限定名,实现类的全限定名之间用换行符分隔。

1
2
com.moralok.dubbo.spi.test.Dog
com.moralok.dubbo.spi.test.Cat

进行测试。

1
2
3
4
5
6
7
8
9
public class JavaSPITest {
@Test
void bark() {
System.out.println("Java SPI");
System.out.println("============");
ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);
serviceLoader.forEach(Animal::bark);
}
}

测试结果

1
2
3
4
Java SPI
============
Dog bark...
Cat bark...

Dubbo SPI 示例

Dubbo 并未使用原生的 Java SPI,而是重新实现了一套功能更加强大的 SPI 机制。Dubbo SPI 的配置文件放在 META-INF/dubbo 文件夹下,名称仍然是接口的全限定名,但是内容是“名称->实现类的全限定名”的键值对,另外接口需要标注 SPI 注解。

1
2
dog = com.moralok.dubbo.spi.test.Dog
cat = com.moralok.dubbo.spi.test.Cat

进行测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DubboSPITest {

@Test
void bark() {
System.out.println("Dubbo SPI");
System.out.println("============");
ExtensionLoader<Animal> extensionLoader = ExtensionLoader.getExtensionLoader(Animal.class);
Animal dog = extensionLoader.getExtension("dog");
dog.bark();
Animal cat = extensionLoader.getExtension("cat");
cat.bark();
}
}

测试结果

1
2
3
4
Dubbo SPI
============
Dog bark...
Cat bark...

Dubbo 获取扩展流程图

Dubbo SPI 源码分析

获取 ExtensionLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}

// 从缓存中获取,如果缓存未命中,则创建,保存到缓存并返回
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}

这个方法包含了如下步骤:

  1. 参数校验。
  2. 从缓存 EXTENSION_LOADERS 中获取与拓展类对应的 ExtensionLoader,如果缓存未命中,则创建一个新的实例,保存到缓存并返回。

“从缓存中获取,如果缓存未命中,则创建,保存到缓存并返回”,类似的 getOrCreate 的处理模式在 Dubbo 的源码中经常出现。

EXTENSION_LOADERS 是 ExtensionLoader 的静态变量,保存了“拓展类->ExtensionLoader”的映射关系。

根据 name 获取 Extension

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
public T getExtension(String name) {
return getExtension(name, true);
}

public T getExtension(String name, boolean wrap) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
// 获取默认的拓展实现
return getDefaultExtension();
}
// Holder,用于持有目标对象
final Holder<Object> holder = getOrCreateHolder(name);
// 双重检查
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 创建拓展实例,设置到 holder 中。
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
return (T) instance;
}

private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();

private Holder<Object> getOrCreateHolder(String name) {
Holder<Object> holder = cachedInstances.get(name);
if (holder == null) {
cachedInstances.putIfAbsent(name, new Holder<>());
holder = cachedInstances.get(name);
}
return holder;
}

这个方法中获取 Holder 和获取拓展实例都是使用 getOrCreate 的模式。

Holder 用于持有拓展实例。cachedInstances 是 ExtensionLoader 的成员变量,保存了“name->Holder(拓展实例)”的映射关系。

创建 Extension

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
private T createExtension(String name, boolean wrap) {
// 从配置文件中加载所有的拓展类,可得到“name->拓展实现类”的映射关系表
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name)) {
throw findException(name);
}
try {
// 使用 getOrCreate 模式获取拓展实例
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.getDeclaredConstructor().newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 向拓展实例中注入依赖
injectExtension(instance);

// 是否包装默认为 true
if (wrap) {

List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}

if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
// 遍历包装类
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
// 区别于旧版本:支持使用 Wrapper 注解进行匹配
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
// 如果有匹配的包装类,包装拓展实例
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}

// 初始化拓展实例
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}

这个方法包含如下步骤:

  1. 通过 getExtensionClasses 获取所有拓展类
  2. 通过反射创建拓展实例
  3. 向拓展实例中注入依赖
  4. 将拓展实例包装在适配的 Wrapper 对象中
  5. 初始化拓展实例

第一步是加载拓展类的关键,第三步和第四步是 Dubbo IOC 和 AOP 的具体实现。

最后拓展实例的结构如下图。

加载 Extension Class

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
private Map<String, Class<?>> getExtensionClasses() {
// 使用 getOrCreate 模式获取所有拓展类
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}

private Map<String, Class<?>> loadExtensionClasses() {
// 1. 缓存默认拓展名
cacheDefaultExtensionName();

Map<String, Class<?>> extensionClasses = new HashMap<>();

// 遍历加载策略,加载各个策略的目录下的配置文件,获取拓展类
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(),
strategy.overridden(), strategy.excludedPackages());
loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"),
strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
}

return extensionClasses;
}

// 如果存在默认拓展名,提取并缓存
private void cacheDefaultExtensionName() {
// 获取 SPI 注解
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation == null) {
return;
}

String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
// 对 SPI 的 value 内容进行切分
String[] names = NAME_SEPARATOR.split(value);
// 检测 name 是否合法
if (names.length > 1) {
throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if (names.length == 1) {
// 缓存默认拓展名,用于 getDefaultExtension
cachedDefaultName = names[0];
}
}
}

依次处理特定目录

代码参考旧版本更容易理解。处理过程在本质上就是依次加载 META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/ 三个目录下的配置文件,获取拓展类。

1
2
3
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
loadDirectory(extensionClasses, SERVICES_DIRECTORY);

新版本使用原生的 Java SPI 加载 LoadingStrategy,允许用户自定义加载策略。

  1. DubboInternalLoadingStrategy,目录 META-INF/dubbo/internal/,优先级最高
  2. DubboLoadingStrategy,目录 META-INF/dubbo/,优先级普通
  3. ServicesLoadingStrategy,目录 META-INF/services/,优先级最低
1
2
3
4
5
6
7
8
private static volatile LoadingStrategy[] strategies = loadLoadingStrategies();

private static LoadingStrategy[] loadLoadingStrategies() {
// 通过 Java SPI 加载 LoadingStrategy
return stream(load(LoadingStrategy.class).spliterator(), false)
.sorted()
.toArray(LoadingStrategy[]::new);
}

LoadingStrategy 的 Java SPI 配置文件

loadDirectory 方法

loadDirectory 方法先通过 classLoader 获取所有的资源链接,然后再通过 loadResource 方法加载资源。

新版本中 extensionLoaderClassLoaderFirst 可以设置是否优先使用 ExtensionLoader's ClassLoader 获取资源链接。

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
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
// filename = 文件夹路径 + type 的全限定名
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls = null;
ClassLoader classLoader = findClassLoader();

// 区别于旧版本:先从 ExtensionLoader's ClassLoader 获取资源链接,默认为 false
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
urls = extensionLoaderClassLoader.getResources(fileName);
}
}

// 根据文件名加载所有同名文件
if (urls == null || !urls.hasMoreElements()) {
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
}

if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
// 加载资源
loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}

loadResource 方法

loadResource 方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass 方法进行其他操作。loadClass 方法用于操作缓存。

新版本中 excludedPackages 可以设置将指定包内的类都排除。

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
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
String clazz = null;
// 按行读取配置内容
while ((line = reader.readLine()) != null) {
// 定位 # 字符,截取 # 字符之前的内容,# 字符之后的内容为注释,需要忽略
final int ci = line.indexOf('#');
if (ci >= 0) {
line = line.substring(0, ci);
}
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
// 定位 = 字符,以 = 字符为界,截取键值对
int i = line.indexOf('=');
if (i > 0) {
name = line.substring(0, i).trim();
clazz = line.substring(i + 1).trim();
} else {
clazz = line;
}
if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)) {
// 加载类,并通过 loadClass 进行缓存
// 区别于旧版本:根据 excludedPackages 判断是否排除
loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException(
"Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL +
", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}

loadClass 方法

loadClass 方法设置了多个缓存,比如 cachedAdaptiveClass、cachedWrapperClasses、cachedNames 和 cachedClasses。

新版本中 overridden 可以设置是否覆盖 cachedAdaptiveClass、cachedClasses 的 name->clazz。

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
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) throws NoSuchMethodException {
// 检测 clazz 是否合法
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 检测 clazz 是否有 Adaptive 注解,有则设置 cachedAdaptiveClass 缓存
// 区别于旧版本:根据 overriden 判断是否可覆盖
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) {
// 检测 clazz 是否是 Wrapper 类型,是则添加到 cachedWrapperClasses 缓存
cacheWrapperClass(clazz);
} else {
// 检测 clazz 是否有默认的构造器方法,如果没有,则抛出异常
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
// 如果 name 为空,则尝试从 Extension 注解中获取 name,或者使用小写的类名(可能截取 type 后缀)作为 name
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException(
"No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}

// 切分 name
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// 如果 clazz 有 Activate 注解,则缓存 names[0]->Activate 的映射关系
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 缓存 clazz->n 的映射关系
cacheName(clazz, n);
// 缓存 n->clazz 的映射关系,传递到最后,终于轮到 extensionClasses
// 区别于旧版本:根据 overriden 判断是否可覆盖
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}

private void cacheAdaptiveClass(Class<?> clazz, boolean overridden) {
if (cachedAdaptiveClass == null || overridden) {
// 可以设置是否覆盖
cachedAdaptiveClass = clazz;
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: "
+ cachedAdaptiveClass.getName()
+ ", " + clazz.getName());
}
}

private void cacheWrapperClass(Class<?> clazz) {
if (cachedWrapperClasses == null) {
cachedWrapperClasses = new ConcurrentHashSet<>();
}
cachedWrapperClasses.add(clazz);
}

private void cacheActivateClass(Class<?> clazz, String name) {
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(name, activate);
} else {
// support com.alibaba.dubbo.common.extension.Activate
com.alibaba.dubbo.common.extension.Activate oldActivate =
clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
if (oldActivate != null) {
cachedActivates.put(name, oldActivate);
}
}
}

Dubbo IOC

Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先通过反射获取目标类的所有方法,然后遍历方法列表,检测方法名是否具有 setter 方法特征并满足条件,若有,则通过 objectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。

与 Spring IOC 相比,Dubbo IOC 实现的依赖注入功能更加简单,代码也更加容易理解。

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
private T injectExtension(T instance) {
// 检测是否有 objectFactory
if (objectFactory == null) {
return instance;
}
try {
// 遍历目标类的所有方法
// 区别于旧版本:增加了对 DisbaleInject、Inject 注解的处理
for (Method method : instance.getClass().getMethods()) {
// 检测是否是 setter 方法
if (!isSetter(method)) {
continue;
}

// 检测是否标注 DisableInject 注解
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}

// 检测参数类型是否是原始类型
Class<?> pt = method.getParameterTypes()[0];
if (ReflectUtils.isPrimitives(pt)) {
continue;
}

// 获取属性名
String property = getSetterProperty(method);
// 检测是否标注 Inject 注解
Inject inject = method.getAnnotation(Inject.class);
if (inject == null) {
injectValue(instance, method, pt, property);
} else {
// 检测 Inject 是否启动、是否按照类型注入
if (!inject.enable()) {
continue;
}

if (inject.type() == Inject.InjectType.ByType) {
injectValue(instance, method, pt, null);
} else {
injectValue(instance, method, pt, property);
}
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}

// 获取属性名
private String getSetterProperty(Method method) {
return method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
}

// public、set 开头、只有一个参数
private boolean isSetter(Method method) {
return method.getName().startsWith("set")
&& method.getParameterTypes().length == 1
&& Modifier.isPublic(method.getModifiers());
}

private void injectValue(T instance, Method method, Class<?> pt, String property) {
try {
// 从 ObjectFactory 中获取依赖对象
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
// 通过反射调用 setter 方法设置依赖
method.invoke(instance, object);
}
} catch (Exception e) {
logger.error("Failed to inject via method " + method.getName()
+ " of interface " + type.getName() + ": " + e.getMessage(), e);
}
}

objectFactory 是 ExtensionFactory 的自适应拓展,通过它获取依赖对象,本质上是根据目标拓展类获取 ExtensionLoader,然后获取其自适应拓展,过程代码如下。具体我们不再深入分析,可以参考Dubbo SPI 自适应拓展的工作原理。

1
2
3
4
5
6
7
8
9
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension();
}
}
return null;
}

参考文章