Dubbo 的扩展点加载从 JDK 标准的 SPI 扩展点发展而来;相比于 JDK 的 SPI,Dubbo 的扩展点改进了以下几个问题(引用自 Dubbo 官网):
- JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源
- 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因
- 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点
Dubbo 扩展点实现的核心是 ExtensionLoader,默认会从以下目录加载扩展点实现
- META-INF/dubbo/internal/
- META-INF/dubbo/
- META-INF/services/
使用 Dubbo SPI 的常用姿势一般有两种:
- 获取自适应扩展,然后在运行时由自适应扩展根据运行时参数调用真正的扩展实现
- 直接获取扩展实现
获取自适应扩展的示例如下:
ConfiguratorFactory configuratorFactory = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class).getAdaptiveExtension()
此处获取的自适应扩展或者由框架自动生成或者由扩展服务提供者自行实现
直接或者扩展实现的示例如下:
Merger m = ExtensionLoader.getExtensionLoader(Merger.class).getExtension(name)
两中方式的区别主要是自适应扩展根据运行时参数动态调用不同的扩展实现;
接下来根据以上代码示例逐个方法分析其实现
ExtensionLoader#getExtensionLoader 实现原理
该方法获取扩展加载器
前面几行的校验逻辑如下:
if (type == null) {
throw new IllegalArgumentException("Extension type == null");
}
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type +
") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
}
- 扩展点非空
- 扩展点必须为接口
- 扩展点必须有 SPI 注解修饰
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);
}
缓存里面没有则创建 ExtensionLoader 并放入缓存,其中的 ExtensionLoader 实例化的代码如下:
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
第一步存储扩展点类型,第二部获取 ExtensionFactory 的自适应扩展(关于这一步后面再讲)
ExtensionLoader#getAdaptiveExtension 的实现
Object instance = cachedAdaptiveInstance.get()
从自适应扩展缓存中获取,如果获取到了就直接返回
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
缓存里面没有则执行如下逻辑:
- 检查 createAdaptiveInstanceError 是否为空, 如果不为空则表示之前获取自适应扩展失败过,直接抛出异常
- cachedAdaptiveInstance 加锁重新从缓存里获取(典型的双检锁的用法),还是没有获取到,则调用 createAdaptiveExtension 方法创建自适应扩展并缓存起来
- 如果 创建自适应扩展发生异常,则将异常存储在 createAdaptiveInstanceError 变量中,并抛出 IllegalStateException 异常
接下来分析 createAdaptiveExtension 方法的实现,代码如下:
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
这段代码核心的两个方法为 getAdaptiveExtensionClass 以及 injectExtension.其中 getAdaptiveExtensionClass 方法获取自适应扩展类,而 injectExtension 负责扩展点的依赖注入;getAdaptiveExtensionClass 的代码如下:
private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses();
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
- getExtensionClasses 加载扩展点的实现类
- 如果 cachedAdaptiveClass 不为空则直接返回
- 否则调用 createAdaptiveExtensionClass 创建自适应扩展类,并缓存在 cachedAdaptiveClass
看到这个地方有的小伙伴可能会觉得奇怪,为什么 getExtensionClasses 就可以去检查一下 cachedAdaptiveClass 是不是为空,为空的时候又用 createAdaptiveExtensionClass 去赋值,这个地方是这样的,自适应扩展有两种方式:
- 由扩展点提供者自己实现,并在实现类上打上 @Adaptive 注解
- 如果没有提供实现,则由系统自动生成(即由 createAdaptiveExtensionClass 生成)
我们先看一下 getExtensionClasses 的实现,该方法主要是从文件中加载扩展点的实现类,并缓存起来,核心的一句代码如下:
loadExtensionClasses()
该方法的实现如下:
private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName();
Map<String, Class<?>> extensionClasses = new HashMap<>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}
这个方法处理了上文提到的扩展点加载的三个位置( META-INF/dubbo/internal/、META-INF/dubbo/、META-INF/services/),其中加载 com.ablibaba 的处理是为了兼容,因为 Dubbo 2.7 和 2.7 之前的版本的包路径改了,2.7 及以后的版本以 org.apache 打头(Dubbo 2.7 为 Dubbo 在 Apache 孵化器的毕业版本),2.7 之前的版本以 com.ablibaba 打头;接下来我们看下 loadDirectory 方法的处理逻辑
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
String fileName = dir + type;
try {
Enumeration<java.net.URL> urls;
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception occurred when loading extension class (interface: " +
type + ", description file: " + fileName + ").", t);
}
}
该方法获取指定路径下资源的 url,并在循环里面加载,加载的方法如下:
loadResource(extensionClasses, classLoader, resourceURL)
这个方法负责解析扩展点实现的配置文件,进行一行一行的解析并通过如下这行代码加载扩展点的实现类
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
接下来一点点的解析 loadClass 方法的实现
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
} else if ...
判断扩展点的实现类是否有 @Adaptive 注解,如果有则缓存自适应扩展的实现类,这里就解释了在 getAdaptiveExtensionClass 方法里为什么先判断 cachedAdaptiveClass 是否为空,不为空则返回的疑问
...
else if (isWrapperClass(clazz)) {
cacheWrapperClass(clazz);
}
判断扩展点实现是不是包装类(即是否有入参为扩展点的构造方法),涉及到 SPI 的 AOP 的实现,后面会讲到
...
else {
clazz.getConstructor();
if (StringUtils.isEmpty(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)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);
saveInExtensionClass(extensionClasses, clazz, n);
}
}
}
既不是自适应扩展实现又不是扩展点的包装类实现,必须有无参构造方法.接下来缓存扩展点实现类的名称,并缓存扩展点的实现类;至此,从配置文件中加载扩展点实现的逻辑就完成了.接下来我们继续看自动生成自适应扩展实现 createAdaptiveExtensionClass 方法的代码
private Class<?> createAdaptiveExtensionClass() {
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}
这个方法做两件事情
- 生成自适应扩展实现类的代码
- 通过动态编译将生成的代码进行编译加载
扩展点接口方法上打了 @Adaptive 才会自动生成自适应扩展的逻辑(即在运行时动态调用扩展点实现),否则就是简单的抛出 UnsupportedOperationException,下面是一个生成出来的自适应扩展实现的示例代码:
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
}
生成的自适应扩展方法里面的逻辑总结起来如下:
- 参数或参数的属性里面去寻找 Url 类型的变量
- 从找到的 Url 里面获取扩展点的名称
- 调用 ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(extName) 获取扩展点的真正实现
- 调用扩展点实现对应的方法
createAdaptiveExtensionClass 方法剩余的几行代码通过动态编译将生成的代码编译成 Class 并加载到 JVM 里面
ClassLoader classLoader = findClassLoader();
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
前面提到自适应扩展有两种方式,一种是在自定义的自适应扩展类上打上 @Adaptive 注解,一种是系统自动生成;在 Dubbo 中只有两个扩展点不是自动生成的,一个是上面代码的 Compiler,另一个是 AdaptiveExtensionFactory.那么 Compiler 的自适应扩展为什么没有自动生成呢?所有生成的自适应扩展都需要在这里通过 Compiler 动态编译,所以 Compiler 的自适应扩展必须先存在.
前面分析到 createAdaptiveExtension 涉及到的第一个方法,接下来继续分析另外一个核心方法 injectExtension
if (objectFactory != null)
判断 objectFactory 是否为空,为空则直接返回,前面分析 ExtensionLoader 的构造方法的时候有初始化 objectFactory 的逻辑,如果扩展点为 ExtensionFactory 则 objectFactory 为 null,否则为 objectFactory 的自适应扩展
如果 objectFactory 不为空,则遍历所有的 setter 方法,如果方法上有 @DisableInject 则直接跳过,否则通过 objectFactory 获取对应的属性,不为空则调用 setter 方法,如果为 SPI 扩展点注入一般注入的是扩展点的自适应扩展实现,至此 getAdaptiveExtension 的实现全部分析完成
ExtensionLoader#getExtension 的实现
该方法根据扩展点的名称获取指定扩展点的实现
第一步判断名字不能为空
if ("true".equals(name)) {
return getDefaultExtension();
}
如果 name 是 true,则获取默认的扩展点实现,getDefaultExtension 方法里面也会去调用加载扩展点实现类的方法,防止扩展点实现类没有加载;
final Holder<Object> holder = getOrCreateHolder(name);
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name);
holder.set(instance);
}
}
}
从缓存中获取扩展点的实例,缓存里面不存在则调用 createExtension 创建扩展点实现类的实例,创建扩展点实例的逻辑如下:
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw findException(name);
}
获取名为 name 的扩展点的实现类,没有则抛出异常
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
缓存里面获取扩展点实现类的实例,没有则通过反射创建
injectExtension(instance);
扩展点实现类自动注入(IOC)
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
}
}
return instance;
扩展点包装实现类的处理(AOP),Dubbo spi 的 AOP 处理实际上是通过类似装饰器模式去实现的,在包装类里面添加特定的处理逻辑然后再委托调用扩展点实现类对应的方法
总结
Dubbo 的 spi 机制实现了:
- 按需加载
- 扩展点延时初始化,只有在真正要去获取扩展点实现的实例时才去初始化
- 扩展点支持 IOC, AOP
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2019-04-25 JVM(HotSpot) 7种垃圾收集器的特点及使用场景
2019-04-25 Redis性能优化