前言
dubbo是微服务架构常用的框架,但是光会用是不行的,还要深入了解它的原理,学习它的思想和书写规范。本文是初步了解dubbo内核,附部分源码解读。后续会深入了解dubbo源码。
一、总结
1.dubbo简介
- dubbo内核:由“微服务+插件”组成。
- dubbo内核的构成:SPI、Adaptive、Wrapper和Activate。
SPI是微内核,其他三个是插件。
dubbo的设计原则(实现原理):AOP、IOC与动态编译Compiler。
所以, AOP、 IoC,与动态编译 Compiler 又称为 Dubbo 内核原理。
2.JDK的SPI机制与DubboSPI机制的不同
2.1 SPI
Service Provider Interface,服务提供者接口,是一种服务发现机制。
JDK 的 SPI 规范规定:
- 接口名:可随意定义
- 实现类名:可随意定义
- 提供者配置文件路径:其查找的目录为 META-INF/services
- 提供者配置文件名称:接口的全限定性类名,没有扩展名。
- 提供者配置文件内容:该接口的所有实现类的全限类性类名写入到该文件中,一个类名占一行
2.2 Dubbo 的 SPI 规范
- 接口名:可以随意定义
- 实现类名:在接口名前添加一个用于表示自身功能的“标识前辍”字符串
- 提供者配置文件路径:在依次查找的目录为
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
- 提供者配置文件名称:接口的全限定性类名,无需扩展名
- 提供者配置文件内容:文件的内容为 key=value 形式,value 为该接口的实现类的全限类性类名,key 可以随意,但一般为该实现类的“标识前辍”(首字母小写)。一个类名占一行。
- 提供者加载:ExtensionLoader 类相当于 JDK SPI 中的 ServiceLoader 类,用于加载提供者配置文件中所有的实现类,并创建相应的实例。
两者不同文档有详细介绍;主要区别是:
- dubbo可以配置文件是key=value,value是实现类的全限定名称,key是作为该实现类的“”标识前缀“。
- JDK的SPI机制会加载已配置接口的所有实现类;而Dubbo的SPI机制可以选择指定实现类加载。
3.dubbo的注解
3.1.@SPI
没有其他功能,就是一个标识注解,表明它是一个SPI接口。
注:没有@SPI注解的接口,在getExtension(Class<T> type) 时,会抛出异常。源码如下:
3.2 Adaptive 机制
Adaptive 机制,即扩展类的自适应机制。即 ExtentionLoader 在加载扩展类时,若没有指定要加载的扩展名,则会直接加载默认的扩展类。即其会自动匹配,做到自适应。其是通过@Adaptive 注解实现的。
dubbo框架中有自适应类的接口就两个:
- ExtensionFactory:它的自适应类——AdaptiveExtensionFactory
- Compiler:它的自适应类——AdaptiveCompiler
@Adaptive 注解除了这两个是修饰类的,其他的都是修饰方法的。
3.2.1 Adaptive用法
- @SPI("wechat")
- 标注在类上
- 标注在方法上
3.3Wrapper 机制(暂时只做了解)
Wrapper 机制,即扩展类的包装机制。就是对扩展类中的 SPI 接口方法进行增强,进行包装,是 AOP 思想的体现,是 Wrapper 设计模式(装饰者设计模式)的应用。一个 SPI 可以包含多个 Wrapper。
总结:
4.Dubbo 的 SPI 源码初步解析
4.1 生成Protocol
![](https://i-blog.csdnimg.cn/blog_migrate/11c0d786bc9ef53d0fce4349989bc5a7.png)
继而调用ExtensionLoader(Protocol.class)方法:
![](https://i-blog.csdnimg.cn/blog_migrate/2b7f24e6b293f2e0ec066eb6465e54d5.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ba4574543e9f2ae1e66a1105594c160f.png)
这里就很重要了,红框的部分,它又调用了我们第一步调用的ExtensionLoader.getExtensionLoader(ExtesionFactory.class)。
也就是说,生成Protocol的整个过程,其实调用了两次SPI。
![](https://i-blog.csdnimg.cn/blog_migrate/86ce90567583dda87d05c61cd5f6ebe2.png)
4.1.2 再来看这个方法的后半部分
![](https://i-blog.csdnimg.cn/blog_migrate/1fa4138310755efa22e5bde75a777601.png)
![](https://i-blog.csdnimg.cn/blog_migrate/e6a08172024d02a32c81ba2e285d71e0.png)
看到这个代码,首先我们应该想到什么呢?——双重检查锁,单例模式都会用到但是双重检查锁有线程安全问题,所以我们会想到什么?——相关属性一定是用volatile修饰的
![](https://i-blog.csdnimg.cn/blog_migrate/04c19c827a6c3b006b10c57be68886c1.png)
- getAdaptiveExtensionClass()获取到adaptive类的class
- injectExtension()用于完成IOC注入
3.接下来调用getAdaptiveExtensionClass()
this.getExtensionClasses()作用:将普通扩展类,adaptive类,wrapper类,active类进行缓存。
4.再进入getExtensionClasses()方法进行查看
cachedClasses缓存的是普通扩展类与activate类,不包含adaptive类与wrapper类
5.继续追到loadExtensionClasses()方法中
this.cacheDefaultExtensionName():缓存SPI接口的默认扩展名。
loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type):加载指定接口的资源。
6.接下来进入loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type)中:
fileName是什么呢?
——就是类路径下的接口名,如下图
思考:为什么命名是复数urls?因为不止这一处有此文件,其他模块下也会有一模一样的文件名。例如:org.apache.dubbo.common.extension.ExtensionFactory在dubbo-comon和dubbo-config-spring中都有。
7.继续跟loadResource()方法:
因屏幕截不下,把代码复制上来:
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8));
Throwable var5 = null;
try {
String line;
try {
while((line = reader.readLine()) != null) {
int ci = line.indexOf(35);
if (ci >= 0) {
line = line.substring(0, ci);
}
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf(61);
if (i > 0) {
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
this.loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable var19) {
IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + this.type + ", class line: " + line + ") in " + resourceURL + ", cause: " + var19.getMessage(), var19);
this.exceptions.put(line, e);
}
}
}
} catch (Throwable var20) {
var5 = var20;
throw var20;
}
} finally {
if (reader != null) {
if (var5 != null) {
try {
reader.close();
} catch (Throwable var18) {
var5.addSuppressed(var18);
}
} else {
reader.close();
}
}
}
} catch (Throwable var22) {
logger.error("Exception occurred when loading extension class (interface: " + this.type + ", class file: " + resourceURL + ") in " + resourceURL, var22);
}
}
高亮的两处代码有一些疑问:
在其他版本中,int类型35和61会被字符串‘#’和‘=’代替,难道可以等价替换?
8.继续跟loadClass方法
可以直观的看到缓存了cacheAdaptiveClass,cacheWrapperClass。并调用cacheActivateClass()缓存了activateClass。isWrapperClass(clazz)方法:判断该类是否是wrapper类
从代码可以分析,只要含有有参构造方法,且参数是SPI接口,就认为该类是wrapper类。
wrapper类代码示例:
![](https://i-blog.csdnimg.cn/blog_migrate/f9fad9557064199651c9c669da09ab96.png)
10.cacheActivateClass() 缓存activete类,兼容2.6
11.saveInExtensionClass()
可以看出,其是将当前 name 与扩展类配对后放到了 extensionClasses 中了。
4.1.3 获取 Protocol 的 extensionLoader
前面的代码执行完毕,然后一层层的返回,最终就返回到了这里。
此时 Protocol 的 ExtensionLoader 的 objectFactory 实例就创建完毕。返回其调用语句。