Dubbo 源码解析 -- SPI 机制详解(一)
什么是 SPI
SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。
- 在 JDK 中有一个查找服务实现的工具类
java.util.ServiceLoader它实现了根据 classpath 下的 META-INF/services/ 目录中创建的以服务接口命名的配置文件,并根据这个配置文件里实现类的全限定名进行类的加载实例化。- Dubbo 中并没有使用原生的 Java SPI 机制,而是对其进行了增强1。
Dubbo 中 SPI 实现方法
我们先看一个简单的 Dubbo 中 SPI 实现的实例
- 先定义一个接口
SpiScanner
@SPI
public interface SpiScanner {
void sayHello();
}
- 接下来定义两个实现类,分别为
SpiScannerA和SpiScannerB
public class SpiScannerA implements SpiScanner {
@Override
public void sayHello() {
System.out.println("Hello, I am SpiScannerA.");
}
}
public class SpiScannerB implements SpiScanner {
@Override
public void sayHello() {
System.out.println("Hello, I am SpiScannerB.");
}
}
- 在 META-INF/dubbo 中建立一个以 SpiScanner 全限定名命名的文件,并在其中加上两行配置
SpiScannerA = com.dubbo.test.spi.SpiScannerA
SpiScannerB = com.dubbo.test.spi.SpiScannerB
了解过 Java SPI 机制的朋友应该看出不同了,在 Dubbo 中关于实现类的配置是以键值对的方式进行配置的,这样可以按需加载制定的实现类。
public class DubboSPITest {
@Test
public void sayHello() throws Exception {
ExtensionLoader<SpiScanner> extensionLoader =
ExtensionLoader.getExtensionLoader(SpiScanner.class);
SpiScanner spiScannerA = extensionLoader.getExtension("SpiScannerA");
spiScannerA.sayHello();
SpiScanner spiScannerB = extensionLoader.getExtension("SpiScannerB");
spiScannerB.sayHello();
}
}
测试结果如下:

Dubbo SPI 除了支持按需加载接口实现类,还增加了 IOC 和 AOP 等特性,这些特性将会在接下来的源码分析章节中一一进行介绍。
Dubbo SPI 源码分析
在 Dubbo 中 SPI 机制的主要实现逻辑封装在了类 org.apache.dubbo.common.extension.ExtensionLoader 中,在使用时我们先通过 ExtensionLoader 的 getExtensionLoader 方法获取一个制定的 ExtensionLoader 实例,然后再使用 ExtensionLoader 的 getExtension 方法获取实现类对象。
- getExtensionLoader 方法源码:
@SuppressWarnings("unchecked")
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;
}
- getExtension 方法源码
@SuppressWarnings("unchecked")
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();
}
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 方法源码
@SuppressWarnings("unchecked")
private T createExtension(String name, boolean wrap) {
// 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
// 通过反射创建实例
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
// 向实例中注入依赖
injectExtension(instance);
// 是否需要判断包装类
if (wrap) {
List<Class<?>> wrapperClassesList = new ArrayList<>();
if (cachedWrapperClasses != null) {
wrapperClassesList.addAll(cachedWrapperClasses);
wrapperClassesList.sort(WrapperComparator.COMPARATOR);
Collections.reverse(wrapperClassesList);
}
if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
// 循环创建 Wrapper 实例
for (Class<?> wrapperClass : wrapperClassesList) {
Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
if (wrapper == null
|| (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
}
}
// 若接口继承了 Lifecycle,则调用 initialize() 方法
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
在这一步逻辑稍微复杂一些,简单来说包含了以下的几个步骤
- 通过 getExtensionClasses 方法获取到配置文件中所有实例化类
- 通过反射创建拓展对象
- 向拓展对象中注入依赖
- 若需要判断包装类则将拓展对象包裹在相应的 Wrapper 对象中
- 若接口继承了 Lifecycle,则调用 initialize() 方法
以上的几个步骤中第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。在接下来的章节中,将会重点分析 getExtensionClasses 方法的逻辑,以及简单介绍 Dubbo IOC 的具体实现。
获取接口的全部实现类
Dubbo SPI 在通过名称获取全部的实现类之前,首先会根据配置文件解析出实现类项的名称和实现类的关系映射表(Map<String, Class<?>>),之后再根据实现类的项名称取出对应的实现类。
- getExtensionClasses 方法源码
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 方法源码
private Map<String, Class<?>> loadExtensionClasses() {
// 缓存默认的实现类对象名(标注在 @SPI 注解上)
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 遍历读取文件的路径策略,通过反射取出所有 LoadingStrategy 实现类,默认的有
// DubboExternalLoadingStrategy - META-INF/dubbo/external/
// DubboInternalLoadingStrategy - META-INF/dubbo/internal/
// DubboLoadingStrategy - META-INF/dubbo/
// ServicesLoadingStrategy - META-INF/services/
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;
}
loadExtensionClasses 方法主要做了两件事情,一是对 @SPI 注解进行解析,缓存默认的实现类名,二是调用 loadDirectory 方法加载制定路径下的文件夹配置文件。
- loadDirectory 方法源码
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();
// 首先尝试使用 ExtendionLoader 的 ClassLoader 加载类
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);
}
}
loadDirectory 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource 方法加载资源。
- loadResource 方法源码
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;
// 按行读取配置内容
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();
line = line.substring(i + 1).trim();
}
if (line.length() > 0 && !isExcluded(line, excludedPackages)) {
// 加载类,并通过 loadClass 方法对类进行缓存
loadClass(extensionClasses, resourceURL, Class.forName(line, 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);
}
}
loadResource 方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass 方法进行其他操作。
- loadClass 方法源码
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
boolean overridden) throws NoSuchMethodException {
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.");
}
// 检测目标类上是否有 Adaptive 注解
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 设置 cachedAdaptiveClass 缓存
cacheAdaptiveClass(clazz, overridden);
}
// 检测 clazz 是否是 Wrapper 类型(检查是否是包装类)
else if (isWrapperClass(clazz)) {
// 设置包装类缓存
cacheWrapperClass(clazz);
} else {
// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {
// 如果 name 为空,则尝试从 Extension 注解中获取 name,或使用小写的类名作为 name
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// 如果类上有 Activate 注解,则使用 names 数组的第一个元素作为键,
// 存储 name 到 Activate 注解对象的映射关系
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 存储 Class 到名称的映射关系
cacheName(clazz, n);
// 存储名称到 Class 的映射关系
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}
可以看到,loadClass 方法操作了不同的缓存,如 cacheAdaptiveClass、cacheWrapperClass、cacheActivateClass、cacheName、saveInExtensionClass。除此之外,该方法没有什么其他特殊的逻辑了。
到此,关于缓存类加载的过程就分析完了。整个过程没什么特别复杂的地方,大家按部就班的分析即可,同时这里基本也包括了 Dubbo SPI 的大部分逻辑。可以看出 Dubbo 这部分的逻辑还是比较容易理解的,Dubbo SPI 可以说是 Dubbo 可扩展性的核心。大家如果有不懂的地方可以调试一下。
Dubbo IOC
Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。
- injectExtension 方法源码
private T injectExtension(T instance) {
if (objectFactory == null) {
return instance;
}
try {
for (Method method : instance.getClass().getMethods()) {
// 判断是否是 set 方法,通过参数唯一,修饰符 public,方法名是否是以 set 开头三个条件判断
if (!isSetter(method)) {
continue;
}
// 判断方法是否加注了 @DisableInject 注解
if (method.getAnnotation(DisableInject.class) != null) {
continue;
}
// 获取方法的入参类型
Class<?> pt = method.getParameterTypes()[0];
// 如果入参为基本类型,则跳过
if (ReflectUtils.isPrimitives(pt)) {
continue;
}
try {
// 获取 set 方法的属性名,如 setAge 则返回 age
String property = getSetterProperty(method);
// 通过 SPI 机制获取方法的入参对象
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);
}
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
return instance;
}
在上面代码中,objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。
Dubbo IOC 目前仅支持 setter 方式注入,总的来说,逻辑比较简单易懂。
总结
总的来说 Dubbo SPI 机制理解起来并不是非常复杂,这里给大家展示了一下示例并对 Dubbo SPI 加载实现类的过程做了简单的分析。有不懂的地方其实尝试下自己调试基本都可以找到答案。但 Dubbo SPI 作为 Dubbo 框架的重要组成部分还有一个非常重要的原因即 Dubbo SPI 的扩展自适应机制,该机制的逻辑比较负责,我想在下一篇文章中进行分析。
如果我在哪一部分写的不够到位或者写错了,还请在座的各位大佬提出意见。
Footnotes
-
本篇主要讲解 Dubbo 中的 SPI 机制,想要了解 Java 原生 SPI 的朋友请阅读其他文章。 ↩