04 Dubbo SPI 精析,接口实现两极反转(下)¶
在上一课时,我们一起学习了 JDK SPI 的基础使用以及核心原理,不过 Dubbo 并没有直接使用 JDK SPI 机制,而是借鉴其思想,实现了自身的一套 SPI 机制,这就是本课时将重点介绍的内容。
Dubbo SPI¶
在开始介绍 Dubbo SPI 实现之前,我们先来统一下面两个概念。
- 扩展点 :通过 SPI 机制查找并加载实现的接口(又称“扩展接口”)。前文示例中介绍的 Log 接口、com.mysql.cj.jdbc.Driver 接口,都是扩展点。
- 扩展点实现 :实现了扩展接口的实现类。
通过前面的分析可以发现,JDK SPI 在查找扩展实现类的过程中,需要遍历 SPI 配置文件中定义的所有实现类,并将这些实现类全部实例化。如果配置文件中定义了多个实现类,而我们只需要其中一个,就会生成不必要的对象。
例如,org.apache.dubbo.rpc.Protocol 接口有 InjvmProtocol、DubboProtocol、RmiProtocol、HttpProtocol、HessianProtocol、ThriftProtocol 等多个实现。如果使用 JDK SPI,就会加载全部实现类,造成资源浪费。
Dubbo SPI 不仅解决了上述资源浪费问题,还改进了 SPI 配置文件的扩展和修改方式。
首先,Dubbo 按照 SPI 配置文件的用途,将其分成了三类目录。
- META-INF/services/ 目录:该目录下的 SPI 配置文件用来兼容 JDK SPI 。
- META-INF/dubbo/ 目录:该目录用于存放用户自定义 SPI 配置文件。
- META-INF/dubbo/internal/ 目录:该目录用于存放 Dubbo 内部使用的 SPI 配置文件。
然后,Dubbo 将 SPI 配置文件改成了 KV 格式,例如:
其中 key 被称为扩展名(也就是 ExtensionName),当我们在为一个接口查找具体实现类时,可以指定扩展名来选择相应的扩展实现。例如,这里指定扩展名为 dubbo,Dubbo SPI 就知道我们要使用:org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类,只实例化这一个扩展实现即可,无须实例化 SPI 配置文件中的其他扩展实现类。
使用 KV 格式的 SPI 配置文件的另一个好处是:让我们更容易定位到问题。假设我们使用的一个扩展实现类所在的 jar 包没有引入到项目中,那么 Dubbo SPI 在抛出异常的时候,会携带该扩展名信息,而不是简单地提示扩展实现类无法加载。这些更加准确的异常信息降低了排查问题的难度,提高了排查问题的效率。
下面我们正式进入 Dubbo SPI 核心实现的介绍。
1. @SPI 注解¶
Dubbo 中某个接口被 @SPI 注解修饰时,就表示该接口是 扩展接口,前文示例中的 org.apache.dubbo.rpc.Protocol 接口就是一个扩展接口:
@SPI 注解的 value 值指定了默认的扩展名称,例如,在通过 Dubbo SPI 加载 Protocol 接口实现时,如果没有明确指定扩展名,则默认会将 @SPI 注解的 value 值作为扩展名,即加载 dubbo 这个扩展名对应的 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol 这个扩展实现类,相关的 SPI 配置文件在 dubbo-rpc-dubbo 模块中,如下图所示:
那么,ExtensionLoader 是如何处理 @SPI 注解的呢?
ExtensionLoader 位于 dubbo-common 模块中的 extension 包中,功能类似于 JDK SPI 中的 java.util.ServiceLoader。Dubbo SPI 的核心逻辑几乎都封装在 ExtensionLoader 之中(其中就包括 @SPI 注解的处理逻辑),其使用方式如下所示:
这里首先来了解一下 ExtensionLoader 中三个核心的静态字段。
- strategies(
LoadingStrategy[]类型):LoadingStrategy 接口有三个实现(通过 JDK SPI 方式加载),分别对应前面介绍的三个 Dubbo SPI 配置目录。这些实现都继承了Prioritized优先级接口,默认优先级如下:
- EXTENSION_LOADERS(ConcurrentMap
类型) :Dubbo 中一个扩展接口对应一个 ExtensionLoader 实例,该集合缓存了全部 ExtensionLoader 实例,其中的 Key 为扩展接口,Value 为加载其扩展实现的 ExtensionLoader 实例。 - EXTENSION_INSTANCES(ConcurrentMap<Class<?>, Object>类型) :该集合缓存了扩展实现类与其实例对象的映射关系。在前文示例中,Key 为 Class,Value 为 DubboProtocol 对象。
下面我们再来关注一下 ExtensionLoader 的实例字段。
-
type(Class<?>类型) :当前 ExtensionLoader 实例负责加载扩展接口。
-
cachedDefaultName(String 类型) :记录了 type 这个扩展接口上 @SPI 注解的 value 值,也就是默认扩展名。
-
cachedNames(ConcurrentMap<Class<?>, String>类型) :缓存了该 ExtensionLoader 加载的扩展实现类与扩展名之间的映射关系。
-
cachedClasses(Holder<Map<String, Class<?>>>类型) :缓存了该 ExtensionLoader 加载的扩展名与扩展实现类之间的映射关系。cachedNames 集合的反向关系缓存。
-
cachedInstances(
ConcurrentMap<String, Holder>类型):缓存该 ExtensionLoader 加载的扩展名与扩展实现对象之间的映射关系。
ExtensionLoader.getExtensionLoader() 方法会根据扩展接口从 EXTENSION_LOADERS 缓存中查找相应的 ExtensionLoader 实例,核心实现如下:
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;
}
得到接口对应的 ExtensionLoader 对象之后会调用其 getExtension() 方法,根据传入的扩展名称从 cachedInstances 缓存中查找扩展实现的实例,最终将其实例化后返回:
public T getExtension(String name) {
// getOrCreateHolder()方法中封装了查找cachedInstances缓存的逻辑
Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) { // double-check防止并发问题
synchronized (holder) {
instance = holder.get();
if (instance == null) {
// 根据扩展名从SPI配置文件中查找对应的扩展实现类
instance = createExtension(name);
holder.set(instance);
}
}
}
return (T) instance;
}
在 createExtension() 方法中完成了 SPI 配置文件的查找以及相应扩展实现类的实例化,同时还实现了自动装配以及自动 Wrapper 包装等功能。其核心流程是这样的:
- 获取
cachedClasses缓存,根据扩展名获取对应的扩展实现类。如果cachedClasses尚未初始化,则扫描前面介绍的三个 SPI 目录,加载其中的扩展实现类,并将扩展名与实现类的映射记录到缓存中。这部分逻辑位于loadExtensionClasses()和loadDirectory()方法。 - 根据扩展实现类从
EXTENSION_INSTANCES缓存中查找实例。如果查找失败,则通过反射创建扩展实现对象。 - 自动装配:为扩展实现对象注入属性(即调用 setter)。这部分涉及
ExtensionFactory,后文会详细介绍。 - 自动包装:使用 Wrapper 类包装扩展实现对象,相关内容将在后文介绍。
- 如果扩展实现类实现了
Lifecycle接口,则在initExtension()方法中调用initialize()完成初始化。
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name); // --- 1
if (clazz == null) {
throw findException(name);
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz); // --- 2
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
injectExtension(instance); // --- 3
Set<Class<?>> wrapperClasses = cachedWrapperClasses; // --- 4
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
initExtension(instance); // ---5
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
type + ") couldn't be instantiated: " + t.getMessage(), t);
}
}
2. @Adaptive 注解与适配器¶
@Adaptive 注解用来实现 Dubbo 的适配器功能,那什么是适配器呢?这里我们通过一个示例进行说明。Dubbo 中的 ExtensionFactory 接口有三个实现类,如下图所示,ExtensionFactory 接口上有 @SPI 注解,AdaptiveExtensionFactory 实现类上有 @Adaptive 注解。
AdaptiveExtensionFactory 不实现任何具体的功能,而是用来适配 ExtensionFactory 的 SpiExtensionFactory 和 SpringExtensionFactory 这两种实现。AdaptiveExtensionFactory 会根据运行时的一些状态来选择具体调用 ExtensionFactory 的哪个实现。
@Adaptive 注解还可以加到接口方法之上,Dubbo 会动态生成适配器类。例如,Transporter 接口有两个被 @Adaptive 注解修饰的方法:
@SPI("netty")
public interface Transporter {
@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException;
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
Dubbo 会生成一个 Transporter$Adaptive 适配器类,该类继承了 Transporter 接口:
public class Transporter$Adaptive implements Transporter {
public org.apache.dubbo.remoting.Client connect(URL arg0, ChannelHandler arg1) throws RemotingException {
// 必须传递 URL 参数
if (arg0 == null) throw new IllegalArgumentException("url == null");
URL url = arg0;
// 确定扩展名,优先从 URL 中的 client 参数获取,其次是 transporter 参数
// 这两个参数名称由@Adaptive 注解指定,最后是@SPI 注解中的默认值
String extName = url.getParameter("client",
url.getParameter("transporter", "netty"));
if (extName == null)
throw new IllegalStateException("...");
// 通过 ExtensionLoader 加载 Transporter 接口的指定扩展实现
Transporter extension = (Transporter) ExtensionLoader
.getExtensionLoader(Transporter.class)
.getExtension(extName);
return extension.connect(arg0, arg1);
}
... // 省略 bind()方法
}
生成 Transporter$Adaptive 类的逻辑位于 ExtensionLoader.createAdaptiveExtensionClass() 方法。相关代码涉及 Javassist 等知识,后续课时会继续介绍。
明确了 @Adaptive 注解的作用之后,我们回到 ExtensionLoader.createExtension() 方法。扫描 SPI 配置文件时,会调用 loadClass() 方法加载其中指定的类,如下图所示:
loadClass() 方法中会识别加载扩展实现类上的 @Adaptive 注解,将该扩展实现的类型缓存到 cachedAdaptiveClass 这个实例字段上(volatile修饰):
private void loadClass(){
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 缓存到 cachedAdaptiveClass 字段
cacheAdaptiveClass(clazz, overridden);
} else ... // 省略其他分支
}
我们可以通过 ExtensionLoader.getAdaptiveExtension() 方法获取适配器实例,并将其缓存到 cachedAdaptiveInstance 字段(Holder 类型)中,核心流程如下:
- 检查
cachedAdaptiveInstance是否已经缓存适配器实例。如果已缓存,则直接返回。 - 调用
getExtensionClasses(),触发前文介绍的loadClass()方法,完成cachedAdaptiveClass的填充。 - 如果存在 @Adaptive 注解修饰的扩展实现类,则直接通过
newInstance()实例化;否则调用createAdaptiveExtensionClass()扫描扩展接口中方法上的 @Adaptive 注解,动态生成适配器类后再实例化。 - 调用
injectExtension()进行自动装配,得到完整的适配器实例。 - 将适配器实例缓存到
cachedAdaptiveInstance,然后返回该实例。
getAdaptiveExtension() 的流程涉及多个方法,这里不再重复粘贴代码。除此之外,还可以通过 API(addExtension() 方法)设置 cachedAdaptiveClass 字段,指定适配器类型。
适配器本身不承担具体业务,而是根据参数和运行状态选择合适的扩展实现。
3. 自动包装特性¶
一个扩展接口可能有多个扩展实现类,这些实现类可能包含相同逻辑。如果在每个实现类中重复编写,代码会很难维护。
Dubbo 的自动包装特性将多个扩展实现类的公共逻辑抽象到 Wrapper 类中。Wrapper 类同样实现扩展接口,在获取真正的扩展实现对象时,Dubbo 会在其外层逐层包装 Wrapper 对象,可以将其理解为装饰器。
了解 Wrapper 类的基本功能后,再回到 ExtensionLoader.loadClass() 方法:
private void loadClass(){
... // 省略前面对@Adaptive 注解的处理
} else if (isWrapperClass(clazz)) { // ---1
cacheWrapperClass(clazz); // ---2
} else ... // 省略其他分支
}
isWrapperClass()方法判断扩展实现类是否包含拷贝构造函数,即构造函数只有一个参数且参数类型为扩展接口。满足条件的类就是 Wrapper 类。- 将 Wrapper 类记录到
cachedWrapperClasses(Set<Class<?>>类型)字段中缓存。
前面介绍 createExtension() 方法时提到的第 4 步,会遍历全部 Wrapper 类,将它们逐层包装到真正的扩展实例外层:
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass
.getConstructor(type).newInstance(instance));
}
}
4. 自动装配特性¶
在 createExtension() 方法中我们看到,Dubbo SPI 在拿到扩展实现类的对象(以及 Wrapper 类的对象)之后,还会调用 injectExtension() 方法扫描其全部 setter 方法,并根据 setter 方法的名称以及参数的类型,加载相应的扩展实现,然后调用相应的 setter 方法填充属性,这就实现了 Dubbo SPI 的自动装配特性。简单来说,自动装配属性就是在加载一个扩展点的时候,将其依赖的扩展点一并加载,并进行装配。 下面简单看一下 injectExtension() 方法的具体实现:
private T injectExtension(T instance) {
if (objectFactory == null) { // 检测 objectFactory 字段
return instance;
}
for (Method method : instance.getClass().getMethods()) {
... // 如果不是 setter 方法,忽略该方法(略)
if (method.getAnnotation(DisableInject.class) != null) {
continue; // 如果方法上明确标注了@DisableInject 注解,忽略该方法
}
// 根据 setter 方法的参数,确定扩展接口
Class<?> pt = method.getParameterTypes()[0];
... // 如果参数为简单类型,忽略该 setter 方法(略)
// 根据 setter 方法的名称确定属性名称
String property = getSetterProperty(method);
// 加载并实例化扩展实现类
Object object = objectFactory.getExtension(pt, property);
if (object != null) {
method.invoke(instance, object); // 调用 setter 方法进行装配
}
}
return instance;
}
injectExtension() 方法实现的自动装配依赖 ExtensionFactory(即 objectFactory 字段)。前面提到过,ExtensionFactory 有两个真正的实现:SpringExtensionFactory 和 SpiExtensionFactory,另一个实现 AdaptiveExtensionFactory 是适配器。
SpiExtensionFactory¶
根据扩展接口获取相应的适配器,不使用属性名称:
@Override
public <T> T getExtension(Class<T> type, String name) {
if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
// 查找 type 对应的 ExtensionLoader 实例
ExtensionLoader<T> loader = ExtensionLoader
.getExtensionLoader(type);
if (!loader.getSupportedExtensions().isEmpty()) {
return loader.getAdaptiveExtension(); // 获取适配器实现
}
}
return null;
}
SpringExtensionFactory¶
将属性名称作为 Spring Bean 的名称,从 Spring 容器中获取 Bean:
public <T> T getExtension(Class<T> type, String name) {
... // 检查:type 必须为接口且必须包含@SPI 注解(略)
for (ApplicationContext context : CONTEXTS) {
// 从 Spring 容器中查找 Bean
T bean = BeanFactoryUtils.getOptionalBean(context,name,type);
if (bean != null) {
return bean;
}
}
return null;
}
5. @Activate 注解与自动激活特性¶
这里以 Dubbo 中的 Filter 为例说明自动激活特性。org.apache.dubbo.rpc.Filter 接口有很多扩展实现类:一个场景可能需要几个 Filter 协同工作,另一个场景则可能需要另外几个实现类。为当前场景指定可用的 Filter 实现,就是 @Activate 注解要解决的问题。
@Activate 注解标注在扩展实现类上,包含 group、value 和 order 三个属性:
group:指定实现类在 Provider 端还是 Consumer 端激活。value:只有当 URL 参数中出现指定的 key 时,才激活该实现类。order:确定扩展实现类的排序。
先看 loadClass() 方法对 @Activate 的扫描。它会将包含 @Activate 注解的实现类缓存到 cachedActivates 字段(Map<String, Object> 类型,Key 为扩展名,Value 为 @Activate 注解):
private void loadClass(){
if (clazz.isAnnotationPresent(Adaptive.class)) {
// 处理@Adaptive 注解
cacheAdaptiveClass(clazz, overridden);
} else if (isWrapperClass(clazz)) { // 处理 Wrapper 类
cacheWrapperClass(clazz);
} else { // 处理真正的扩展实现类
clazz.getConstructor(); // 扩展实现类必须有无参构造函数
...// 兜底:SPI 配置文件中未指定扩展名称,则用类的简单名称作为扩展名(略)
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
// 将包含@Activate 注解的实现类缓存到 cachedActivates 集合中
cacheActivateClass(clazz, names[0]);
for (String n : names) {
// 在 cachedNames 集合中缓存实现类->扩展名的映射
cacheName(clazz, n);
// 在 cachedClasses 集合中缓存扩展名->实现类的映射
saveInExtensionClass(extensionClasses, clazz, n,
overridden);
}
}
}
}
getActivateExtension() 方法会使用 cachedActivates 集合。该方法的参数中,url 包含配置信息,values 是配置中指定的扩展名,group 为 Provider 或 Consumer。核心逻辑如下:
- 获取默认激活的扩展集合。默认激活的扩展实现类必须满足以下条件:存在于
cachedActivates集合中;@Activate 的group与当前group匹配;扩展名未出现在values中;URL 中出现了 @Activate 指定的 key。 - 按照 @Activate 的
order属性对默认激活的扩展集合排序。 - 按顺序添加自定义扩展实现类的对象。
public List<T> getActivateExtension(URL url, String[] values,
String group) {
List<T> activateExtensions = new ArrayList<>();
// values 配置就是扩展名
List<String> names = values == null ?
new ArrayList<>(0) : asList(values);
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {// ---1
getExtensionClasses(); // 触发 cachedActivates 等缓存字段的加载
for (Map.Entry<String, Object> entry :
cachedActivates.entrySet()) {
String name = entry.getKey(); // 扩展名
Object activate = entry.getValue(); // @Activate 注解
String[] activateGroup, activateValue;
if (activate instanceof Activate) { // @Activate 注解中的配置
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else {
continue;
}
if (isMatchGroup(group, activateGroup) // 匹配 group
// 没有出现在 values 配置中的,即为默认激活的扩展实现
&& !names.contains(name)
// 通过"-"明确指定不激活该扩展实现
&& !names.contains(REMOVE_VALUE_PREFIX + name)
// 检测 URL 中是否出现了指定的 Key
&& isActive(activateValue, url)) {
// 加载扩展实现的实例对象,这些都是激活的
activateExtensions.add(getExtension(name));
}
}
// 排序 --- 2
activateExtensions.sort(ActivateComparator.COMPARATOR);
}
List<T> loadedExtensions = new ArrayList<>();
for (int i = 0; i < names.size(); i++) { // ---3
String name = names.get(i);
// 通过"-"开头的配置明确指定不激活的扩展实现,直接就忽略了
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
if (DEFAULT_KEY.equals(name)) {
if (!loadedExtensions.isEmpty()) {
// 按照顺序,将自定义的扩展添加到默认扩展集合前面
activateExtensions.addAll(0, loadedExtensions);
loadedExtensions.clear();
}
} else {
loadedExtensions.add(getExtension(name));
}
}
}
if (!loadedExtensions.isEmpty()) {
// 按照顺序,将自定义的扩展添加到默认扩展集合后面
activateExtensions.addAll(loadedExtensions);
}
return activateExtensions;
}
最后举个简单的例子说明上述处理流程。假设 cachedActivates 集合缓存的扩展实现如下表所示:
在 Provider 端调用 getActivateExtension() 方法时传入的 values 配置为 "demoFilter3、-demoFilter2、default、demoFilter1",那么根据上面的逻辑:
- 得到默认激活的扩展实现集合:[demoFilter4, demoFilter6];
- 排序后为 [ demoFilter6, demoFilter4 ];
- 按序添加自定义扩展实例之后得到 [ demoFilter3, demoFilter6, demoFilter4, demoFilter1 ]。
总结¶
本课时我们深入全面地讲解了 Dubbo SPI 的核心实现:首先介绍了 @SPI 注解的底层实现,这是 Dubbo SPI 最核心的基础;然后介绍了 @Adaptive 注解与动态生成适配器类的核心原理和实现;最后分析了 Dubbo SPI 中的自动包装和自动装配特性,以及 @Activate 注解的原理。
Dubbo SPI 是 Dubbo 框架实现扩展机制的核心,希望你仔细研究其实现,为后续源码分析过程打下基础。
也欢迎你在留言区分享你的学习心得和实践经验。





