文章目录
第一章Dubbo概述 https://blog.csdn.net/gqs2019/article/details/115366306
第二章 Dubbo系统框架搭建 https://blog.csdn.net/gqs2019/article/details/115370178
第三章 Dubbo高级配置 https://blog.csdn.net/gqs2019/article/details/115403364
第四章 Dubbo的系统架构解析 https://blog.csdn.net/gqs2019/article/details/115456656
第五章Dubbo 的内核解析 https://blog.csdn.net/gqs2019/article/details/115457046
第六章 Dubbo的源码解析 https://blog.csdn.net/gqs2019/article/details/115460065
所谓 Dubbo 的内核是指,Dubbo 中所有功能都是基于它之上完成的,都是由它作为基
础的。dubbo 的内核包含四部分:SPI、AOP、IoC,与 Compiler。
一、JDK的SPI是什么?
- 简介
SPI,Service Provider Interface,服务提供者接口,是一种服务发现机制。 - JDK 的 SPI 规范
JDK 的 SPI 规范规定:
? 接口名:可随意定义
? 实现类名:可随意定义
? 提供者配置文件路径:其查找的目录为 META-INF/services
? 提供者配置文件名称:接口的全限定性类名,没有扩展名。
? 提供者配置文件内容:该接口的所有实现类的全限类性类名写入到该文件中,一个类名
占一行 - 代码举例 12-jdkspi
(1) 定义工程
创建一个 Maven 的 Java 工程。
(2) 定义接口
(3) 定义两个实现类
(4) 创建目录与配置文件
(5) 定义配置文件内容
(6) 定义服务消费者类
二、Dubbo 的 SPI
Dubbo 并没有直接使用 JDK 的 SPI,而是在其基础之上对其进行了改进。
- 规范说明
Dubbo 的 SPI 规范是:
? 接口名:可以随意定义
? 实现类名:在接口名前添加一个用于表示自身功能的“标识前辍”字符串
? 提供者配置文件路径:在依次查找的目录为
META-INF/dubbo/internal
META-INF/dubbo
META-INF/services
? 提供者配置文件名称:接口的全限定性类名,无需扩展名
? 提供者配置文件内容:文件的内容为 key=value 形式,value 为该接口的实现类的全限类
性类名,key 可以随意,但一般为该实现类的“标识前辍”(首字母小写)。一个类名占
一行。
? 提供者加载:ExtensionLoader 类相当于 JDK SPI 中的 ServiceLoader 类,用于加载提供者
配置文件中所有的实现类,并创建相应的实例。
- Dubbo 的 SPI 举例 13-dubbospi
下面将实现一个下单功能。其支付方式仅支持支付宝或微信两种方式。即这里要定义一
个 SPI 接口,其存在两个扩展类。
(1) 创建工程
创建一个 Maven 的 Java 工程。
(2) 导入依赖
导入 Dubbo 的依赖。
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--Dubbo 依赖-->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
(3) 定义 SPI 接口
(4) 定义两个扩展类
(5) 定义扩展类配置文件
(6) 定义测试类
三、Adaptive 机制
Adaptive 机制,即扩展类的自适应机制。即其可以指定想要加载的扩展名,也可以不指
定。若不指定,则直接加载默认的扩展类。即其会自动匹配,做到自适应。其是通过@Adaptive
注解实现的。一个 SPI 只能有一个 Adaptive 类。
- @Adaptive 注解
@Adaptive 注解可以修饰类与方法,其作用相差很大。
(1) @Adaptive 修饰类
被@Adapative 修饰的 SPI 接口扩展类称为 Adaptive 类,表示该 SPI 扩展类会按照该类中
指定的方式获取,即用于固定实现方式。其是装饰者设计模式的应用。
(2) @Adaptive 修饰方法
被@Adapative 修饰 SPI 接口中的方法称为 Adaptive 方法。在 SPI 扩展类中若没有找到
Adaptive 类,但系统却发现了 Adapative 方法,就会根据 Adaptive 方法自动为该 SPI 接口动
态生成一个 Adaptive 扩展类,并自动将其编译。例如 Protocol 接口中就包含两个 Adaptive
方法。 - Adaptive 类代码举例 14-adaptiveclass
(1) 创建工程
复制 13-dubbospi 工程,在其基础之上修改。
(2) 定义 adaptive 扩展类
(3) 修改扩展类配置文件
(4) 测试 1
(5) 测试 2
- Adaptive 方法规范
下面我们准备要定义 Adaptive 方法。那么 Adaptive 方法的定义有什么要求呢?我们通
过查看动态生成的 Adaptive 类来总结 Adaptive 方法的要求。
(1) 动态生成 Adaptive 类格式package <SPI 接口所在包>; public class SPI 接口名$Adpative implements SPI 接口 { public adaptiveMethod (arg0, arg1, ...) { // 注意,下面的判断仅对 URL 类型,或可以获取到 URL 类型值的参数进行判断 // 例如,dubbo 的 Invoker 类型中就包含有 URL 属性 if(arg1==null) throw new IllegalArgumentException(异常信息); if(arg1.getUrl()==null) throw new IllegalArgumentException(异常信息); URL url = arg1.getUrl(); // 其会根据@Adaptive 注解上声明的 Key 的顺序,从 URL 获取 Value, // 作为实际扩展类。若有默认扩展类,则获取默认扩展类名;否则获取 // 指定扩展名名。 String extName = url.get 接口名() == null?默认扩展前辍名:url.get 接口名(); if(extName==null) throw new IllegalStateException(异常信息); SPI 接口 extension = ExtensionLoader.getExtensionLoader(SPI 接口.class) .getExtension(extName); return extension. adaptiveMethod(arg0, arg1, ...); } public unAdaptiveMethod( arg0, arg1, ...) { throw new UnsupportedOperationException(异常信息); } }
(2) 方法规范
从前面的动态生成的 Adaptive 类中的 adaptiveMethod()方法体可知,其对于要加载的扩
展名的指定方式是通过 URL 类型的方法参数指定的。所以对于 Adaptive 方法的定义规范仅
一条:其参数包含 URL 类型的参数,或参数可以获取到 URL 类型的值。方法调用者是通过
URL 传递要加载的扩展名的。
4. Adaptive 方法代码举例 14-adaptivemethod
(1) 创建工程
复制 14-adaptiveclass 工程,在其基础之上修改。
(2) 修改 SPI 接口
(3) 修改两个扩展类
(4) 删除原来的 Adaptive 类
若存在 Adaptive 类,即使定义了 Adaptive 方法,其执行的也是 Adaptive 类,所以这里
要首先将 Adaptive 类删除。
(5) 定义扩展类配置文件
由于 Adaptive 类已经删除,所以在配置文件中也需要将 Adaptive 类的注册也删除。
(6) 测试 1
(7) 测试 2
四、Wrapper 类规范
Wrapper 机制不是通过注解实现的,而是通过 Wrapper 类实现的。
Wrapper 类在定义时需要遵循如下规范。
? 该类要实现 SPI 接口
? 该类中要有 SPI 接口的引用
? 在接口实现方法中要调用 SPI 接口引用对象的相应方法
? 该类名称一般以 Wrapper 结尾(不是必需的)
代码举例 15-wrapper
复制 14-adaptivemethod 工程,在其基础之上修改。
(1) 定义两个 wrapper 类
(2) 修改扩展类配置文件
五、Activate 机制
用于激活扩展类的。
Activate 机制,即扩展类的激活机制。通过指定的条件来激活当前的扩展类。其是通过
@Activate 注解实现的。
@Activate 注解
在@Activate 注解中共有五个属性,其中 before、after 两个属性已经过时,剩余有效属
性还有三个。它们的意义为:
? group:为扩展类指定所属的组别,是当前扩展类的一个标识。String[]类型,表示一个
扩展类可以属于多个组。
? value:为当前扩展类指定的 key,是当前扩展类的一个标识。String[]类型,表示一个扩
展类可以有多个指定的 key。
? order:指定筛选条件相同的扩展类的加载顺序。序号越小,优先级越高。默认值为 0。
order 值相同,则按照注册顺序的逆序进行加载。
代码举例
(1) 创建工程
复制 13-dubbospi 工程,在其基础之上修改。
(2) 修改扩展类 AlipayOrder
(3) 修改扩展类 WeChatOrder
(4) 定义扩展类 CardOrder
(5) 定义扩展类 CashOrder
(6) 定义扩展类 CouponOrder
(7) 注册扩展类
(8) 测试
六、总结
在配置文件中可能会存在四种类:普通扩展类,Adaptive 类,Wrapper 类,及添加了
@Activate 注解的扩展类。它们的共同点是,都实现了 SPI 接口。
? 一个 SPI 接口的 Adaptive 类只能有一个(无论是否是自动生成的),而 Wrapper 类与
Activate 类都可以有多个。
? 只有普通扩展类与 Activate 类属于扩展类,而 Adaptive 与 Wrapper 类均不属于扩展类
? Adaptive、Activate 都是通过注解实现的,而 Wrapper 则不是。
七、Dubbo 的 SPI 源码解析
下面以 Spring 容器的获取过程为例来解析 SPI 的执行过程。
1.总思路
2. 代码解析
从 ServiceConfig 入手
(1) factory 实例的 loader 的 extensionFactory 实例是 null
这个 objectFactory 实例也是通过 SPI 获取到的。
(2) 获取 factory 的 extensionLoader
构造器执行完毕,则 ExtensionFactory 的 extensionLoader 实例就创建完毕。重新返回其
调用语句。
(3) 创建 Container 的 loader 的 extensionFactory
跟踪执行 getAdaptiveExtension()。
返回 loadExtensionClasses()方法。
跟踪 cacheAdaptiveClass()方法。
跟踪 cacheWrapperClass()方法。
重新返回 loadClass()方法
跟踪 cacheActiveClass()方法
跟踪 cacheName()方法。
跟踪 saveInExtensionClass()方法。
可以看出,其是将当前 name 与扩展类配对后放到了 extensionClasses 中了。
(4) 获取 Protocol 的 extensionLoader
前面的代码执行完毕,然后一层层的返回,最终就返回到了这里。
此时 Protocol 的 ExtensionLoader 的 objectFactory 实例就创建完毕。返回其调用语句。
最后第 131 行返回这个新创建的 loader,返回给该 getExtensionLoader()方法的调用语句。
(5) 获取 Protocol 的默认扩展类实例
此时再跟踪第 121 行的 getAdaptiveExtension()方法,是通过刚才获取到的 Protocol 的
laoder 对象获取 Protocol 的自适合实例。其执行过程与前面的相同。
八、Dubbo 的 IoC 源码解析
跟踪 getExtension()方法。
使用 Spring 容器获取。
九、Dubbo 的 AOP 源码解析
那么,它们是如何包装 instance 实例的呢?这就要分析这些 Wrapper 类了。
ProtocolFilterWrapper 解析
下面以 ProtocolFilterWrapper 为例进行解析
然后我们再查看 Protocol 接口中包含的方法。
然后再逐个查看 ProtocolFilterWrapper 类中对这些方法的重写,即对于原 Protocol 实例
的增强。
十、Dubbo 的动态编译 Compile 源码解析
Dubbo 的动态编译器有两种:JavassistCompile 与 JdkCompiler。在 Dubbo 框架中,只要
是动态编译,使用的都是默认的 JavassistCompiler,而编译器 JdkCompiler 仅是让用户可以进
行自由选择使用的。所以我们在这里只分析 JavassistCompile 编译器。
1 Javassist 简介
Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库。一般情况下,对字节码文
件进行修改是需要使用虚拟机指令的。而使用 Javassist,可以直接使用 java 编码的形式,而
不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
2 手工实现 Javassist 动态编译 17-javassist
(1) 创建工程
创建一个 Maven 的 Java 工程。
(2) 导入依赖
仅需要一个 Javassist 依赖。
3) 定义 JavassistCompiler
3 解析 Dubbo 的动态编译
下面以获取 Protocol 接口的 adaptive 扩展类实例为例进行解析。
此时查看 code 的值,其为一个字符串,其内容就是动态生成的 Adaptive 扩展类。
在其上右击,选择 copy value 即可复制该字符串的内容。
动态生成 Adaptive 代码后,就可以对其进行编译了。
以下内容均来自,开课吧老雷,感谢老雷
第一章Dubbo概述 https://blog.csdn.net/gqs2019/article/details/115366306
第二章 Dubbo系统框架搭建 https://blog.csdn.net/gqs2019/article/details/115370178
第三章 Dubbo高级配置 https://blog.csdn.net/gqs2019/article/details/115403364
第四章 Dubbo的系统架构解析 https://blog.csdn.net/gqs2019/article/details/115456656
第五章Dubbo 的内核解析 https://blog.csdn.net/gqs2019/article/details/115457046
第六章 Dubbo的源码解析 https://blog.csdn.net/gqs2019/article/details/115460065