导读
- 想要搞懂Dubbo底层实现,ExtensionLoader是不可绕过得门槛,不能深刻理解其扩展点设计,源码阅读部分会很懵逼!!!(当然,即使扩展点懂了,源码也不一定能看懂,哈哈。玩笑话,意思就是Dubbo得源码还是比较难读的,因为有很多概念与设计如果不弄清楚,基本上会被绕晕。)
- 关键字 :Dubbo 扩展点设计(SPI)、ExtensionLoader
Dubbo 扩展点定义
- 从 ExtensionLoader的源码中,我们可以找到 Dubbo SPI加载的目录有三个:1. META-INF/services/ (标准的SPI路径)2. META-INF/dubbo/ 3. META-INF/dubbo/internal/ (内部实现)
- 扩展点文件定义格式:目录 / 接口全路径
- 文件内容(常用格式)
registry=com.apache.dubbo.registry.integration.RegistryProtocol dubbo=com.apache.dubbo.rpc.protocol.dubbo.DubboProtocol filter=com.apache.dubbo.rpc.protocol.ProtocolFilterWrapper listener=com.apache.dubbo.rpc.protocol.ProtocolListenerWrapper # 没有等号情况 com.apache.dubbo.registry.integration.XxxProtocol com.apache.dubbo.registry.integration.YyyProtocol # 等号前面有多个值得情况 zzz,default=com.apache.dubbo.registry.integration.CustomProtocol
以下解读,作者根据具体的某个扩展点去分析加载过程
Ⅰ. ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtion()
- 先判断出当前
type(Protocol.class)
是否有扩展类加载器,没有就创建一个
- 先判断出当前
- 然后从缓存的
cachedAdaptiveInstance
适配实例Holder中查找,没有的话就创建一个适配类
- 然后从缓存的
- 查找适配类:加载当前扩展点对应的全部扩展实现, META-INF/dubbo/ , META-INF/dubbo/internal/,META-INF/services/几个路径下,扩展点上一定要有
@SPI
注解,并且注解的值只能有一个(默认扩展点
)
- 查找适配类:加载当前扩展点对应的全部扩展实现, META-INF/dubbo/ , META-INF/dubbo/internal/,META-INF/services/几个路径下,扩展点上一定要有
- 如果扩展实现上 带有
@Adaptive
注解,则当作默认的适配类扩展(只能定义一个扩展适配类实现)
- 如果扩展实现上 带有
-
否则尝试获取包装类扩展(带有扩展点接口的构造器),如果有则添加到
wrappers
集合中。此处可以得知(适配类即使是包装类的约束格式,也不会被当作包装类)
-
- 如果没有包装类,则直接获取无参构造器(此处目的是确保扩展实现可以被实例化)
- 判断
=
号前面的扩展实现名称,若为空(等号前面没有值),则取扩展实现类除去后缀的部分并且全部小写(例如:XxxProtocol 取 xxx,YyyProtocol 取 yyy)作为名称name
- 判断
- 再将
name
根据逗号拆分,若扩展实现类上有@Activate
激活注解,则取name[0]作为key,注解作为value,放入cachedActivates
缓存map中。然后边遍历name
,依次添加到cachedNames
(key = 扩展实现class实例,value = name)中
- 再将
- 最后都放入到 当前类型对应得扩展实现中
cachedClasses
- 最后都放入到 当前类型对应得扩展实现中
- 如果缓存得适配类
cachedAdaptiveClass
为空(表示 扩展实现类上有标注 @Adaptive),则需要创建适配类( Protocol 扩展点没有适配类 )
- 如果缓存得适配类
- 获取扩展点
Protocol
所有方法,判断方法上是否存在@Adaptive
注解,若不存在则直接抛异常,否则生成动态得适配类。如下:
- 获取扩展点
package com.apache.dubbo.rpc;
import com.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.apache.dubbo.rpc.Protocol {
public void destroy()
{
throw new UnsupportedOperationException(
"method public abstract void com.apache.dubbo.rpc.Protocol.destroy() of interface com.apache.dubbo.rpc.Protocol is not adaptive method!"
);
}
public int getDefaultPort()
{
throw new UnsupportedOperationException(
"method public abstract int com.apache.dubbo.rpc.Protocol.getDefaultPort() of interface com.apache.dubbo.rpc.Protocol is not adaptive method!"
);
}
public com.apache.dubbo.rpc.Exporter export(com.apache.dubbo.rpc.Invoker arg0) throws com.apache.dubbo.rpc.RpcException
{
if(arg0 == null) throw new IllegalArgumentException("com.apache.dubbo.rpc.Invoker argument == null");
if(arg0.getUrl() == null) throw new IllegalArgumentException(
"com.apache.dubbo.rpc.Invoker argument getUrl() == null");
com.apache.dubbo.common.URL url = arg0.getUrl();
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if(extName == null) throw new IllegalStateException(
"Fail to get extension(com.apache.dubbo.rpc.Protocol) name from url(" + url.toString() +
") use keys([protocol])");
// 获取url中指定得协议,默认是 dubbo协议
com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(
com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0); // 调用真正得协议扩展实现得export方法
}
public com.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.apache.dubbo.common.URL arg1) throws com.apache.dubbo.rpc.RpcException{
if(arg1 == null) throw new IllegalArgumentException("url == null");
com.apache.dubbo.common.URL url = arg1;
// 获取url中指定得协议,默认是 dubbo协议
String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
if(extName == null) throw new IllegalStateException(
"Fail to get extension(com.apache.dubbo.rpc.Protocol) name from url(" + url.toString() +
") use keys([protocol])");
// 获取真实得协议扩展实现
com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(
com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1); // 调用真正得协议扩展实现得refer方法
}
}
- 创建完适配类之后,接着再获取
Compiler
得适配类,用于动态生成Protocol
得适配类(其他动态生成得适配类也是用Compiler
来生成得)。由于Compiler
存在适配类,即扩展点类加载器对应得缓存cachedAdaptiveClass
不为空,直接返回。此处cachedAdaptiveClass = com.apache.dubbo.common.compiler.support.AdaptiveCompiler
- 创建完适配类之后,接着再获取
- 实例化并调用
injectExtension
方法注入其他扩展实例(不适合框架实现得扩展适配类,因为动态生成得适配类基本上不会依赖其他扩展点
):判断生成得适配类中 是否存在类似方法:1. set开头得 2. 只有一个参数类型 3. public访问修饰符,若有,则截取set
之后得首字母小姐得名称将其作为扩展名,然后通过ExtensionFactory
工厂获取此扩展名得实例,调用当前setXXX
方法,叫之为IOC
。由于AdaptiveCompiler
只有一个set方法但是没有对应得 String.class类型得defaultCompiler扩展点
,所以此处不会进行注入:
- 实例化并调用
public static void setDefaultCompiler(String compiler) {
DEFAULT_COMPILER = compiler;
}
- 接着调用适配扩展实现
AdaptiveCompiler
得compile
方法,方法内会获取默认得扩展实现(此处是javassist
扩展名)JavassistCompiler
,然后调用其compile方法动态生成适配class实例。
- 接着调用适配扩展实现
Ⅱ. ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName)
上面生成得适配类中,有一步是获取真正调用扩展实现 :
com.apache.dubbo.rpc.Protocol extension = (com.apache.dubbo.rpc.Protocol)
ExtensionLoader.getExtensionLoader(com.apache.dubbo.rpc.Protocol.class).getExtension(extName);
- 首先还是获取 扩展点
Protocol
对应得扩展类加载器,接着调用getExtension
方法获取指定得扩展实现,先查询是否已经缓存了相应得扩展实例,若没查到会调用createExtension
方法去创建当前指定得扩展实现。 - 调用
createExtension
方法时,先从当前扩展点实现class实例缓存中查找,若是没有这个扩展名,说明传入得参数有问题,会直接抛出异常。然后再从对象缓存实例cachedClasses
中查找是否已经实例化了,没找到,实例化之后放入对象缓存EXTENSION_INSTANCES
中。 - 调用
injectExtension
方法注入其他得扩展点实现(IOC),同上面Step13 - 获取当前扩展点得所有包装类
cachedWrapperClasses
,循环调用包装类得有参构造器(参数就是扩展点类型)实例化,之后也会调用injectExtension
方法为包装类也注入其他得扩展点实现,并返回最后一个包装类得实例(每个Wrapper类都会包装一个之前得扩展实现 即: A -> B(A) -> C(B), 最后返回 C这个包装类)(AOP)。而当前扩展点Protocol
有两个包装类实现:ProtocolFilterWrapper
和ProtocolListenerWrapper
。所以,当获取适配扩展实现时:
- 若是动态生成得,则
getAdaptiveExtion() -> getExtension("dubbo或者是 url.getProtocol()的值")
-> injectExtension(T instance) (真正执行调用的扩展实现)
-> injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance))
( A -> B(A) -> C(B) 最后返回 C这个包装类) - 若是定义了适配类实现,则直接走适配类的逻辑
- 若是动态生成得,则
Ⅲ. ExtensionLoader#addExtension(String name, Class<?> clazz)
- 首先加载所有的扩展类实现,若
clazz
没有实现type
扩展点,抛出异常。 (必须要实现扩展点,才可以添加) - 若
clazz
是个接口,抛出异常。(扩展实现不能是接口,否则不能实例化) - 若
clazz
上没有带有@Adaptive
注解- 若 扩展名
name
为空,抛出异常。 (扩展名不能为空) - 若 缓存扩展实现
cachedClasses
中已经存在当前扩展名,抛出异常。(扩展名不能重复) - 若以上两个条件都不满足,则将扩展名与扩展点建立映射关系 , 缓存到
cachedNames
,cachedClasses
中。
- 若 扩展名
- 若
clazz
上带有@Adaptive
注解- 若适配类
cachedAdaptiveClass
不为空,抛出异常。(扩展点的适配类只能有一个) - 为空的话,将当前
clazz
作为扩展点的适配类,赋值给cachedAdaptiveClass
添加扩展实现的逻辑还是比较简单的。通过API的方式动态添加扩展实现,可以不通过配置文件的方式(挺实用的)
- 若适配类
Ⅳ ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(url, key, group) :
- 找到 ExtensionLoader的 getActivateExtension方法:
// ExtensionLoader
public List<T> getActivateExtension(URL url, String key, String group = consumer) {
// 获取 URL 中指定 key对应的值
// 以 ProtocolFilterWrappe r的 buildInvokerChain 方法为例(任何一个扩展点都可)
// key = REFERENCE_FILTER_KEY,group = consumer
// 可以进行如下改造 ( Filter自定义编排化):
// 1. URL url = invoker.getUrl().addParameter(REFERENCE_FILTER_KEY,"-default");
// 2. URL url = invoker.getUrl().addParameter(REFERENCE_FILTER_KEY,"-cache,actives");
// 3. URL url = invoker.getUrl().addParameter(REFERENCE_FILTER_KEY,"-default,actives");
// 你知道上面几种扩展方式,最后返回的 Filter扩展实现有哪些么 ?
String value = url.getParameter(key);
// value值用于控制激活扩展点是否加载,为空表示不指定激活扩展点名称,从所有的激活扩展实现中根据 @Activate 注解跟指定的组进行筛选,若是需要指定,定义的扩展点名称需要以 "," 分隔开
return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
}
// ExtensionLoader
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<>();
List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values);
// 如果指定的扩展的名称不包括 -default(排除SPI中的实现)标识符,则从所有已经加载的激活扩展点中找到满足条件的,否则只加载指定的激活扩展点
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses();
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {// 遍历激活扩展点的所有实现
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
if (isMatchGroup(group, activateGroup) // 如果当前需要加载的组在激活扩展点实现指定的组中才算匹配
&& !names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) { // URL中至少要有 activateValue[] 中的其中一个key,当前激活扩展点实现才会被使用
exts.add(getExtension(name));
}
}
exts.sort(ActivateComparator.COMPARATOR); // 按照自然序排序
}
List<T> usrs = new ArrayList<>();
// 若names不为空,表示指定了扩展点名称,找到不是以排除标识符'-'开头并且不包括要排除(-name)的扩展点名称,获取其扩展实现
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
if (DEFAULT_KEY.equals(name)) {
if (!usrs.isEmpty()) {
exts.addAll(0, usrs);
usrs.clear();
}
} else {
usrs.add(getExtension(name));
}
}
}
if (!usrs.isEmpty()) {
exts.addAll(usrs);
}
return exts;
}
- 上述就是Dubbo源码中经常出现的获取扩展实现的代码,了解了其原理之后,后面我们解读源码时,会更清晰自然了。
- ☛ 文章要是勘误或者知识点说的不正确,欢迎评论,毕竟这也是作者通过阅读源码获得的知识,难免会有疏忽!
- ☛ 要是感觉文章对你有所帮助,不妨点个关注,或者移驾看一下作者的其他文集,也都是干活多多哦,文章也在全力更新中。
- ☛ 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处!