Dubbo作为一款流行的分布式服务框架,其强大的扩展机制让它成为众多Java应用的首选。但是,这其中的秘密究竟是什么?本文将带你穿越Dubbo的魔法之门,深入研究SPI机制,探索其神奇的工作方式,以及如何在你的Dubbo项目中利用它来实现更灵活的架构。
Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。
Dubbo的SPI机制包括以下关键点:
扩展点接口:
com.alibaba.dubbo.rpc.Protocol
是Dubbo中的一个扩展点接口,用于定义协议的实现。使用@SPI
注解:
@SPI
注解来标识默认的实现类。这个注解用于标记一个接口,并在括号中指定默认的实现类的名字。com.alibaba.dubbo.rpc.Protocol
接口上可能会有以下注解:@SPI("dubbo")
public interface Protocol {
// ...
}
这表示Protocol
接口的默认实现类是dubbo
。
配置文件:
META-INF/dubbo/
目录下,但它的作用不同于Java标准的SPI配置文件。Dubbo的SPI配置文件主要用于指定各个扩展点的实现类的别名,而不是具体的实现类。dubbo=com.alibaba.dubbo.rpc.protocol.DubboProtocol
hessian=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
这些配置将不同的实现类与别名关联起来。
使用扩展点:
<dubbo:protocol name="dubbo" />
这表示使用了dubbo
别名对应的com.alibaba.dubbo.rpc.protocol.DubboProtocol
实现类。
在Dubbo中,需要使用@SPI
注解来标识扩展点接口的默认实现类,并且SPI配置文件的作用是指定别名与具体实现类的映射关系。这个机制允许你在配置中轻松切换不同的实现类,而不需要直接在XML配置中写全限定类名。再次为之前的回答的不准确信息道歉。
下面我们从 ExtensionLoader 的 getExtension 方法作为入口,对拓展类对象的获取过程进行详细的分析。
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null");
}
if ("true".equals(name)) {
// 获取默认的拓展实现类
return getDefaultExtension();
}
// Holder,顾名思义,用于持有目标对象
Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
// 双重检查
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 创建拓展实例
instance = createExtension(name);
// 设置实例到 holder 中
holder.set(instance);
}
}
}
return (T) instance;
}
上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。下面我们来看一下创建拓展对象的过程是怎样的。
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)) {
// 循环创建 Wrapper 实例
for (Class<?> wrapperClass : wrapperClasses) {
// 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
// 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
instance = injectExtension(
(T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("...");
}
}
createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:
以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。由于此类设计源码较多,这里简单的总结下ExtensionLoader整个执行逻辑:
getExtension(String name) #根据key获取拓展对象
-->createExtension(String name) #创建拓展实例
-->getExtensionClasses #根据路径获取所有的拓展类
-->loadExtensionClasses #加载拓展类
-->cacheDefaultExtensionName #解析@SPI注解
-->loadDirectory #方法加载指定文件夹配置文件
-->loadResource #加载资源
-->loadClass #加载类,并通过 loadClass 方法对类进行缓存