0%

Dubbo SPI 解析

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。

Dubbo SPI介绍

Dubbo SPI实现了自己的IOC和AOP机制,其中服务的实例被称为Extension(扩展),类似于Spring框架中bean的角色,是一个扩展实现类的实例。Dubbo框架中所有类的实例基本都是使用了SPI的方式获取。所以了解SPI的实现机制是学习Dubbo的重中之重。

Dubbo SPI示例

这里以apache dubbo 2.x版本为例,引入dubbo依赖

1
2
3
4
5
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.15</version>
</dependency>

编写扩展类的接口
@SPI的value为默认实现,@Adpative则为自适应加载注解,在代码运行时根据传入的URL(必传)参数动态加载,这个后面会详细讲解

注:URL.class为Dubbo自定义,全限名:org.apache.dubbo.common.URL

1
2
3
4
5
6
7
8
@SPI("apple")
public interface Fruit {

void printName();

@Adaptive("name")
void printName(URL url);
}

编写Fruit接口的实现类:Apple、Banana、Orange

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Apple implements Fruit {
private static final String _name = "Apple";

@Override
public void printName() {
System.out.println(_name);
}

@Override
public void printName(URL url) {
this.printName();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Banana implements Fruit {
private static final String _name = "Banana";

@Override
public void printName() {
System.out.println(_name);
}

@Override
public void printName(URL url) {
this.printName();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Orange implements Fruit {
private static final String _name = "Orange";

@Override
public void printName() {
System.out.println(_name);
}

@Override
public void printName(URL url) {
this.printName();
}
}

在resources添加目录:META-INF/dubbo

新建文件(dubbo约定文件名必须与接口全限定名一致):com.xx.xx.Fruit

编辑文件内容并保存,建议以kv形式保存,如果像Orange的配置,则Dubbo默认会以类名“orange”作为实现类的名称:

1
2
3
4
apple=com.xx.xx.Apple
banana=com.xx.xx.Banana
# orange=com.xx.xx.Orange
com.xx.xx.Orange

编写Main方法测试运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) {
ExtensionLoader<Fruit> FruitExtensionLoader = ExtensionLoader.getExtensionLoader(Fruit.class);

// 默认扩展对象,由@SPI注解声明
Fruit fruit1 = FruitExtensionLoader.getDefaultExtension();
fruit1.printName();
System.out.println("==============================");

Fruit fruit2 = FruitExtensionLoader.getExtension("banana");
fruit2.printName();
System.out.println("==============================");

Map<String, String> parameters = new HashMap<>(2);
// key在@Adpative中指定,否则默认取类名,例:fruit
parameters.put("name", "orange");
URL url = new URL("", "", 0, parameters);

Fruit fruit3 = FruitExtensionLoader.getAdaptiveExtension();
fruit3.printName(url);
System.out.println("==============================");
}

执行结果:

1
2
3
4
5
6
Apple
==============================
Banana
==============================
Orange
==============================

源码解析

  • 为方便理解,源码会做相应的修改和省略

SPI核心入口ExtensionLoader.getExtensionLoader()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ExtensionLoader示例缓存
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();

******

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
******
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;
}

EXTENSION_LOADERS为ExtensionLoader实例的缓存缓存

看ExtensionLoader构造函数,ExtensionFactory可以近似理解为Spring中的BeanFactory,作为获取Extension实例的工厂,且支持自适应扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private final ExtensionFactory objectFactory;

******

private ExtensionLoader(Class<?> type) {
this.type = type;
// ExtenFactory本身不需要
if (type == ExtensionFactory.class) {
objectFactory = null;
} else {
// 获取对象工厂
objectFactory = ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension();
}
}

查看接口ExtensionFactory的实现类,这里先讲解Dubbo自己的SpiExtensionFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SpiExtensionFactory implements ExtensionFactory {

@Override
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;
}

}

查看loader.getSupportedExtensions()

1
2
3
4
public Set<String> getSupportedExtensions() {
Map<String, Class<?>> clazzes = getExtensionClasses();
return Collections.unmodifiableSet(new TreeSet<>(clazzes.keySet()));
}

进入getExtensionClasses(),这里为一个double check lock实现的单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

******

private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}

进入loadExtensionClasses(),开始装载扩展类

1
2
3
4
5
6
7
8
9
10
11
12
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();

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

for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(),
strategy.overridden(), strategy.excludedPackages());
}

return extensionClasses;
}

strategies使用jdk自带的ServiceLoader装载多种dubbo spi装载方式,本文主要看DubboLoadingStrategy,其中声明了配置文件读取路径为”META-INF/dubbo/“

1
2
3
4
5
6
7
8
public class DubboLoadingStrategy implements LoadingStrategy {

@Override
public String directory() {
return "META-INF/dubbo/";
}

}

进入loadDirectory(),这里会根据LoadingStrategy配置的文件目录,解析并读取

核心执行顺序为:loadDirectory() > loadResource() > loadClass()

加载文件目录 > 加载spi配置文件 > 加载配置文件中的扩展实现类

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
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) {
String fileName = dir + type;
******

loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);

******
}

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
******

loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);

******
}

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name, boolean overridden) throws NoSuchMethodException {
// @Adaptive注解放在类上,则表示类为默认的动态扩展实现
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 缓存到Map
cacheAdaptiveClass(clazz, overridden);
} else
// 构造函数参数唯一且为扩展接口类,则表示该类为包装类(Dubbo的AOP实现)
if (isWrapperClass(clazz)) {
// 缓存到Map
cacheWrapperClass(clazz);
} else {
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
name = findAnnotationName(clazz);
}
// 扩展实现可以有多个名称,使用‘,‘分隔
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
// 缓存到Map
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}

到这,Dubbo SPI已经完成了对扩展接口实现类(注意是class,此时类还并未实例化)的扫描和缓存;

接下来回头看ExtensionLoader#getDefaultExtension

1
2
3
4
5
6
7
8
9
public T getDefaultExtension() {
// 上文已说明,完成扩展类的扫描和缓存
getExtensionClasses();
// cachedDefaultName为@SPI声明
if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
return null;
}
return getExtension(cachedDefaultName);
}

进入getExtension,开始实例化扩展实现类,这里同样使用了double check lock保证单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public T getExtension(String name, boolean wrap) {
if ("true".equals(name)) {
return getDefaultExtension();
}
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name, wrap);
holder.set(instance);
}
}
}
return (T) instance;
}

进入createExtension,开始创建实例并出初始化实例

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 static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);

******

private T createExtension(String name, boolean wrap) {
Class<?> clazz = getExtensionClasses().get(name);
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);
// 包装类(Dubbo AOP)
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);
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;
}

最后获取到扩展接口的默认实现类实例

ExtensionLoader#getExtension()也是同理,这里将扩展实现类的名称已参数的形式传入,而不是读取@SPI的声明,这里不再赘述


接下来看getAdaptiveExtension方法,根据参数获取自适应实现类,这里仍然使用了单例模式

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
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}

synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}

return (T) instance;
}

进入createAdaptiveExtension

1
2
3
4
5
6
7
8
9
10
11
12
13
private T createAdaptiveExtension() {
try {
// 依赖注入
return injectExtension(
// 获取自适应类
(T) getAdaptiveExtensionClass()
// 实例化
.newInstance()
);
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}

进入getAdaptiveExtensionClass

1
2
3
4
5
6
7
8
private Class<?> getAdaptiveExtensionClass() {
// 上文已说明,完成扩展类的扫描和缓存
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

进入createAdaptiveExtensionClass,这里Dubbo为被@Adaptive声明的方法生成自适应实现,即自适应代理类

1
2
3
4
5
6
7
private Class<?> createAdaptiveExtensionClass() {
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
// Dubbo默认使用javassist类编辑器,生成代理类
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}

以上文Demo中的Fruit#printName(URL url)为例,Dubbo生成的代理类源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Fruit$Adaptive implements com.xx.xx.Fruit {
public void printName() {
throw new UnsupportedOperationException("The method public abstract void com.xx.xx.Fruit.printName() of interface com.xx.xxFruit is not adaptive method!");
}

public void printName(org.apache.dubbo.common.URL arg0) {
if (arg0 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg0;
// 根据url中的“name”参数动态获取扩展实例,默认为“apple”
String extName = url.getParameter("name", "apple");
if (extName == null)
throw new IllegalStateException("Failed to get extension (com.xx.xx.Fruit) name from url (" + url.toString() + ") use keys([name])");
com.xx.xx.Fruit extension = (com.xx.xx.Fruit) ExtensionLoader.getExtensionLoader(com.xx.xx.Fruit.class).getExtension(extName);
extension.printName(arg0);
}
}

到这里我们可以看到Dubbo的自适应扩展,其实现方式还是使用了ExtensionLoader.getExtensionLoader(Class).getExtension(name);

参数name则是从URL中获取,key在@Adaptive注解中声明,value则在代码运行时动态输入,Dubbo则依靠这个机制实现动态自适应扩展类加载。


本文只是简单介绍了Dubbo SPI的简单使用和主要实现原理,源码中还有诸多细节,无法全面覆盖,待后续补充 ……