官方文档:dubbo.apache.org/zh/docs/v2.…
基本概念
Java SPI:juejin.cn/post/707559…
Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。
Dubbo 改进了 JDK 标准的 SPI 的以下问题:
- JDK 标准的
SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。 - 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的
ScriptEngine,通过getName()获取脚本类型的名称,但如果RubyScriptEngine因为所依赖的jruby.jar不存在,导致RubyScriptEngine类加载失败,这个失败原因被吃掉了,和ruby对应不起来,当用户执行ruby脚本时,会报不支持ruby,而不是真正失败的原因。 - 增加了对扩展点
IOC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。
约定
在扩展类的 jar 包内,放置扩展点配置文件 META-INF/dubbo/接口全限定名,内容为:配置名=扩展实现类全限定名,多个实现类用换行符分隔。
示例
定义一个接口
@SPI
public interface Robot {
void sayHello();
}
定义2个实现类
public class OptimusPrime implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Optimus Prime.");
}
}
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Bumblebee.");
}
}
在 META-INF/dubbo 文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。其内容如下:
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee
Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader 可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下。与 Java SPI 实现类配置不同,Dubbo SPI 是通过 键值对 的方式进行配置,这样就可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。
public class DubboSPITest {
@Test
public void sayHello() throws Exception {
ExtensionLoader<Robot> extensionLoader =
ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();
}
}
测试结果:
源码分析
首先,通过 ExtensionLoader 的 getExtensionLoader() 方法获取一个 ExtensionLoader 实例(这个方法在3.0版本已经废弃)。再通过 ExtensionLoader 的 getExtension() 方法获取扩展类对象。
//方法入口
public T getExtension(String name) {
//调用重载方法获取实例对象
T extension = getExtension(name, true);
if (extension == null) {
throw new IllegalArgumentException("Not find extension: " + name);
}
return extension;
}
public T getExtension(String name, boolean wrap) {
checkDestroyed();
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
// 获取默认的扩展实现类
return getDefaultExtension();
}
String cacheKey = name;
if (!wrap) {
cacheKey += "_origin";
}
// 获取Holder,顾名思义,用于持有目标对象
final Holder<Object> holder = getOrCreateHolder(cacheKey);
Object instance = holder.get();
// double check
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 创建扩展实例
instance = createExtension(name, wrap);
// 将实例设置到 holder 中
holder.set(instance);
}
}
}
return (T) instance;
}
创建扩展类实例
通过 createExtension() 方法创建扩展实例:
- 通过
getExtensionClasses()获取所有的扩展类 - 通过反射创建扩展对象
- 向扩展对象中注入依赖
- 将扩展对象包裹在相应的
Wrapper对象中
第一个步骤是加载扩展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。
Wrapper 包装类:dubbo.apache.org/zh/docs/v2.…
private T createExtension(String name, boolean wrap) {
// 从配置文件中加载所有的扩展类,可得到“配置项名称”到“配置类”的映射关系表
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null || unacceptableExceptions.contains(name)) {
throw findException(name);
}
try {
T instance = (T) extensionInstances.get(clazz);
if (instance == null) {
// 通过反射创建实例
extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
instance = (T) extensionInstances.get(clazz);
// 在注入依赖之前调用外部接口方法
instance = postProcessBeforeInitialization(instance, name);
// 在实例中注入依赖
injectExtension(instance);
// 在注入依赖之后调用外部接口方法
instance = postProcessAfterInitialization(instance, name);
}
// 包装类
if (wrap) {
// 缓存的 Wrapper class
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);
boolean match = (wrapper == null) ||
((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
!ArrayUtils.contains(wrapper.mismatches(), name));
if (match) {
// 实例化 Wrapper 类并注入依赖
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
// 在注入依赖之后调用外部接口方法
instance = postProcessAfterInitialization(instance, name);
}
}
}
}
// Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
initExtension(instance);
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
加载所有扩展类
在通过名称获取扩展类之前,首先需要根据配置文件解析出扩展项名称到扩展类的映射关系表(Map<名称, 扩展类>),之后再根据扩展项名称从映射关系表中取出相应的扩展类即可。
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() 方法总共做了两件事情,一是对 @SPI 注解进行解析,二是调用 loadDirectory() 方法加载指定文件夹配置文件。
private Map<String, Class<?>> loadExtensionClasses() {
checkDestroyed();
// 解析 @SPI 注解
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
// 加载指定文件夹配置文件
// LoadingStrategy[] strategies 加载策略
for (LoadingStrategy strategy : strategies) {
loadDirectory(extensionClasses, strategy, type.getName());
// compatible with old ExtensionFactory
if (this.type == ExtensionInjector.class) {
loadDirectory(extensionClasses, strategy, ExtensionFactory.class.getName());
}
}
return extensionClasses;
}
用 LoadingStrategy 类去加载指定文件夹下的配置文件。LoadingStrategy 是个接口,通过Java SPI 机制去获取该接口的实现类。其配置在 META-INF/servies/org.apache.dubbo.common.extension.LoadingStrategy。
其三个实现类分别代表的配置文件目录为:
- DubboInternalLoadingStrategy:
META-INF/dubbo/internal/ - DubboLoadingStrategy:
META-INF/dubbo/ - ServicesLoadingStrategy:
META-INF/services/
org.apache.dubbo.common.extension.DubboInternalLoadingStrategy
org.apache.dubbo.common.extension.DubboLoadingStrategy
org.apache.dubbo.common.extension.ServicesLoadingStrategy
解析 @SPI
解析 @SPI 的 value 值。type 变量是在构造 ExtensionLoader 时传入的 服务接口 class 类型。
private void cacheDefaultExtensionName() {
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation == null) {
return;
}
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("More than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if (names.length == 1) {
cachedDefaultName = names[0];
}
}
}
加载指定文件夹配置文件
loadDirectory() 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource() 方法加载资源。
// 方法入口
private void loadDirectory(Map<String, Class<?>> extensionClasses, LoadingStrategy strategy, String type) {
loadDirectory(extensionClasses, strategy.directory(), type, strategy.preferExtensionClassLoader(),
strategy.overridden(), strategy.includedPackages(), strategy.excludedPackages(), strategy.onlyExtensionClassLoaderPackages());
// 兼容com.alibab 开头的包名
String oldType = type.replace("org.apache", "com.alibaba");
loadDirectory(extensionClasses, strategy.directory(), oldType, strategy.preferExtensionClassLoader(),
strategy.overridden(), strategy.includedPackagesInCompatibleType(), strategy.excludedPackages(), strategy.onlyExtensionClassLoaderPackages());
}
// 重载
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
boolean extensionLoaderClassLoaderFirst, boolean overridden, String[] includedPackages,
String[] excludedPackages, String[] onlyExtensionClassLoaderPackages) {
// fileName = 文件夹路径 + type全限定名
String fileName = dir + type;
try {
List<ClassLoader> classLoadersToLoad = new LinkedList<>();
// try to load from ExtensionLoader's ClassLoader first
if (extensionLoaderClassLoaderFirst) {
ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
classLoadersToLoad.add(extensionLoaderClassLoader);
}
}
// load from scope model
Set<ClassLoader> classLoaders = scopeModel.getClassLoaders();
if (CollectionUtils.isEmpty(classLoaders)) {
// 通过ClassLoader获取配置文件的URL(当前配置文件所在的路径)
// file:/D:/workspace/**/target/classes/META-INF/dubbo/org.example.dubbo.provider.spi.Robot
Enumeration<java.net.URL> resources = ClassLoader.getSystemResources(fileName);
if (resources != null) {
while (resources.hasMoreElements()) {
// 加载资源
loadResource(extensionClasses, null, resources.nextElement(), overridden, includedPackages, excludedPackages, onlyExtensionClassLoaderPackages);
}
}
} else {
classLoadersToLoad.addAll(classLoaders);
}
// 每个classLoader都加载一次配置文件,获取该URL
Map<ClassLoader, Set<java.net.URL>> resources = ClassLoaderResourceLoader.loadResources(fileName, classLoadersToLoad);
resources.forEach(((classLoader, urls) -> {
// 方法内部也调用了 loadResource() 方法
loadFromClass(extensionClasses, overridden, urls, classLoader, includedPackages, excludedPackages, onlyExtensionClassLoaderPackages);
}));
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
loadResource() 方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass() 方法进行其他操作。
//加载资源文件
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
java.net.URL resourceURL, boolean overridden, String[] includedPackages, String[] excludedPackages, String[] onlyExtensionClassLoaderPackages) {
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
String line;
String clazz;
//按行读取配置内容
while ((line = reader.readLine()) != null) {
// 定位 # 字符
final int ci = line.indexOf('#');
if (ci >= 0) {
// 截取#之前的内容,#之后的内容为注释,忽略
// a=b # ....
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) && isIncluded(clazz, includedPackages)
&& !isExcludedByClassLoader(clazz, classLoader, onlyExtensionClassLoaderPackages)) {
// 加载类,并通过 loadClass 方法对类进行缓存
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() 方法用于主要用于操作缓存。
@Adaptive扩展点自适应:dubbo.apache.org/zh/docs/v2.…
@Activate扩展点自动激活: dubbo.apache.org/zh/docs/v2.…
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)) {
// 存储 clazz 到 cachedWrapperClasses 缓存中
cacheWrapperClass(clazz);
} else {
if (StringUtils.isEmpty(name)) {
// 如果没有name,则解析 @Extension 注解获取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 到 name 的映射关系
cacheName(clazz, n);
// 存储 name 到 class 的映射关系
saveInExtensionClass(extensionClasses, clazz, n, overridden);
}
}
}
}