前言
Dubbo SPI 机制大家都清楚,通过在META-INF/dubbo.internal 下配置类的全路径,来加载文件中的某一个实现。例如下图:
这篇笔记以注册中心的加载为例,通过源码来看一下dubbo内部是如何使用SPI 机制的
注册中心的调用
首先说下注册中心的初始化步骤,如下图
DubboBootstrap启动器在初始化的时候会去构建一个注册中心复合类CompositeDynamicConfiguration, 可以将其理解为注册中心的父类- dubbo调用注册中心的工厂类
DynamicConfigurationFactory,用于加载具体的注册中心实现DynamicConfigurationFactory使用SPI机制,通过ExtensionLoader.getExtensionLoader 来获取注册中心实现类
ExtensionLoader
DynamicConfigurationFactory做的事情
static DynamicConfigurationFactory getDynamicConfigurationFactory(String name) {
//获取我们配置的SPI文件名称,即:org.apache.dubbo.common.config.configcenter.DynamicConfigurationFactory
Class<DynamicConfigurationFactory> factoryClass = DynamicConfigurationFactory.class;
//获取加载器
ExtensionLoader<DynamicConfigurationFactory> loader = getExtensionLoader(factoryClass);
//传入key值,获取具体的实现类
//这里name=nacos,对应SPI文件中的 nacos=org.apache.dubbo.configcenter.support.nacos.NacosDynamicConfigurationFactory
return loader.getOrDefaultExtension(name);
}
ExtensionLoader.getOrDefaultExtension 的调用流程如下:
getOrDefaultExtension会去加载SPI类,如果找到想要的(name)类,就通过反射去创建类的实例,如果没有就去加载默认的实现类。
首先从系统中加载SPI实现:
loadExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
//这里就是SPI文件中的key、value。
//value是通过Class.forName(line, true, classLoader) 转换成了class对象
//这个map结果集会被缓存起来
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;
}
从map中寻找想要的类:
containsKey
private boolean containsExtension(String name) {
//getExtensionClasses 返回的就是上面的map结果集
return getExtensionClasses().containsKey(name);
}
通过反射创建SPI类的实例:
createExtension
private T createExtension(String name) {
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);
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
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);
}
}
总结
- SPI 的核心处理类是
ExtensionLoader,dubbo将其封装成了一个工具类,只要传入class 和name 就能获取需要的对象。- 我们可以看出,只有在使用的时候dubbo才会去加载需要的SPI对象,而且是将class的创建和实例的创建分开处理,只对你需要的类创建实例。
- 相较于JAVA SPI机制的全量加载实例,dubbo的SPI设计无疑是高效的。