装箱百万奖金,第六届全国工业互联网数据创新应用大赛火热报名中! 了解详情
写点什么

Dubbo 源码解析之 SPI(一):扩展类的加载过程(下)

  • 2020-02-06
  • 本文字数:2307 字

    阅读完需:约 8 分钟

Dubbo源码解析之SPI(一):扩展类的加载过程(下)

接上文

二、Dubbo SPI

2.1 Dubbo SPI 的小栗子

老习惯,在拆解源码之前,先来个栗子。此处示例是在前文例子的基础上稍做了些修改。


1)定义一个接口


修改接口,加上了 Dubbo 的 @SPI 注解。


@SPIpublic interface Operation {        int operate(int num1, int num2);}
复制代码


2)写两个简单的实现


沿用之前的两个实现类。


3)添加一个配置文件


新增配置文件放在 dubbo 目录下。


目录结构


1578290551452077988.png


文件内容


division=com.api.impl.DivisionOperationplus=com.api.impl.PlusOperation
复制代码


4)测试程序


public class DubboSpiTest {    @Test    public void testOperation() throws Exception {          ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Operation.class);          Operation division = loader.getExtension("division");          System.out.println("result: " + division.operate(1, 2));    } }
复制代码


5)测试结果


run division operationresult:0
复制代码


2.2 Dubbo SPI 源码

上面的测试例子也很简单,和 JDK 原生的 SPI 对比来看,Dubbo 的 SPI 可以根据配置的 kv 值来获取。在没有拆解源码之前,考虑一下如何实现。


我可能会用双层 Map 来实现缓存:第一层的 key 为接口的 class 对象,value 为一个 map;第二层的 key 为扩展名(配置文件中的 key),value 为实现类的 class。实现懒加载的方式,当运行方法的时候创建空 map。在真正获取时先从缓存中查找具体实现类的 class 对象,找得到就直接返回、找不到就根据配置文件加载并缓存。


Dubbo 又是如何实现的呢?


2.2.1 getExtensionLoader 方法

首先来拆解 getExtensionLoader 方法。


1578290559734019449.png


这是一个静态的工厂方法,要求传入的类型必须为接口并且有 SPI 的注解,用 map 做了个缓存,key 为接口的 class 对象,而 value 是 ExtensionLoader 对象。


2.2.2 getExtension 方法

再来拆解 ExtensionLoader 的 getExtension 方法。


1578290567703021526.png


这段代码也不复杂,如果传入的参数为’true’,则返回默认的扩展类实例;否则,从缓存中获取实例,如果有就从缓存中获取,没有的话就新建。用 map 做缓存,缓存了 holder 对象,而 holder 对象中存放扩展类。用 volatile 关键字和双重检查来应对多线程创建问题,这也是单例模式的常用写法。


2.2.3 createExtension 方法

重点分析 createExtension 方法。


1578290574548021989.png


这段代码由几部分组成:


  • 根据传入的扩展名获取对应的 class。

  • 根据 class 去缓存中获取实例,如果没有的话,通过反射创建对象并放入缓存。

  • 依赖注入,完成对象实例的初始化。

  • 创建 wrapper 对象。也就是说,此处返回的对象不一定是具体的实现类,可能是包装的对象。


第二个没啥好说的,我们重点来分析一下 1、3、4 三个部分。


1)getExtensionClasses 方法


1578290582077084790.png


老套路,从缓存获取,没有的话创建并加入缓存。这里缓存的是一个扩展名和 class 的关系。这个扩展名就是在配置文件中的 key。创建之前,先缓存了一下接口的限定名。加载配置文件的路径是以下这几个。


1578290589633053357.png


2)loadDirectory 方法


1578290595955080762.png


获取配置文件路径,获取 classLoader,并使用 loadResource 方法做进一步处理。


3)loadResource 方法


1578290603084099601.png


1578290608823055721.png


loadResource 加载了配置文件,并解析了配置文件中的内容。loadClass 方法操作了不同的缓存。


首先判断是否有 Adaptive 注解,有的话缓存到 cacheAdaptiveClass(缓存结构为 class);然后判断是否 wrapperclasses,是的话缓存到 cacheWrapperClass 中(缓存结构为 Set);如果以上都不是,这个类就是个普通的类,存储 class 和名称的映射关系到 cacheNames 里(缓存结构为 Map)。


基本上 getExtensionClasses 方法就分析完了,可以看出来,其实并不是很复杂。


2.2.4 IOC

1)injectExtension 方法


1578290615558058995.png


这个方法实现了依赖注入,即 IOC。首先通过反射获取到实例的方法;然后遍历,获取 setter 方法;接着从 objectFactory 中获取依赖对象;最后通过反射调用 setter 方法注入依赖。


objectFactory 的变量类型为 AdaptiveExtensionFactory。


2)AdaptiveExtensionFactory


1578290622344095754.png


这个类里面有个 ExtensionFactory 的列表,用来存储其他类型的 ExtensionFactory。Dubbo 提供了两种 ExtensionFactory,一种是 SpiExtensionFactory, 用于创建自适应的扩展;另一种是 SpringExtesionFactory,用于从 Spring 的 IOC 容器中获取扩展。配置文件一个在 dubbo-common 模块,一个在 dubbo-config 模块。


配置文件


1578290630935084937.png


1578290637954041289.png


SpiExtensionFactory 中的 Spi 方式前面已经解析过了。


1578290646268070832.png


SpringExtesionFactory 是从 ApplicationContext 中获取对应的实例。先根据名称查找,找不到的话,再根据类型查找。


1578290653724067215.png


依赖注入的部分也拆解完毕,看看这次拆解的最后一部分代码。


2.2.5 AOP

创建 wrapper 对象的部分,wrapper 对象是从哪里来的呢?还记得之前拆解的第一步么,loadClass 方法中有几个缓存,其中 wrapperclasses 就是缓存这些 wrapper 的 class。


1578290661372092365.png


从代码中可以看出,只要构造方法里有且只有唯一参数,同时此参数为当前传入的接口类型,即为 wrapper class。


1578290670597015172.png


此处循环创建 wrapper 实例,首先将 instance 做为构造函数的参数,通过反射来创建 wrapper 对象,然后再向 wrapper 中注入依赖。


看到这里,可能会有人有疑问:为什么要创建一个 wrapper 对象?其实很简单,系统要在真正调用的前后干点别的事呗。这个就有点类似于 spring 的 aop 了。


三、总结

本文简单介绍了 JDK 的 SPI 和 Dubbo 的 SPI 用法,分析了 JDK 的 SPI 源码和 Dubbo 的 SPI 源码。在拆解的过程中可以看出,Dubbo 的源码还是很值得一读的。在实现方面考虑得很周全,不仅有对多线程的处理、多层缓存,也有 IOC、AOP 的过程。不过,Dubbo 的 SPI 就这么简单么?当然不是,这篇只拆解了扩展类的加载过程,Dubbo 的 SPI 中还有个很复杂的扩展点-自适应机制。欲知后事如何,请听下回分解~~


2020-02-06 10:321041

评论

发布
暂无评论
发现更多内容

ESP32-C3入门教程 基础篇(七、LEDC — LED PWM 控制器)

矜辰所致

pwm ESP32-C3 9月月更

Python语法之循环

向阳逐梦

Python 9月月更 循环控制

网安超基础一周目

吉师职业混子

9月月更

从0开始的计算机之路

吉师职业混子

9月月更

汽车总线系统

不脱发的程序猿

汽车电子 CAN总线 汽车总线系统

Spring事务管理

十八岁讨厌编程

Java 后端开发 9月月更

【云原生 | 从零开始学 Kubernetes】三、Kubernetes集群管理工具kubectl

泡泡

Docker 云计算 云原生 k8s 9月月更

【云原生 | 从零开始学Kubernetes】四、Kubernetes之YAML文件详解

泡泡

云计算 云原生 k8s 9月月更

Python语法之列表

向阳逐梦

列表 元素 9月月更

Hybrid App会靠小程序崛起吗

Geek_99967b

小程序

多维分析利器Druid

穿过生命散发芬芳

Druid 9月月更

【网络安全】记一次简单渗透测试实战

网络安全学海

黑客 网络安全 信息安全 渗透测试 漏洞利用

开发者有话说|谈谈自己大学期间的收获,以及毕业的求职经历

向阳逐梦

个人成长 成长路上的思考 初心不变

架构实战营模块一作业

东尼大锤

Java基础科普

吉师职业混子

9月月更

追光动画《杨戬》:水墨、石窟、洛神赋,中式美感背后有中国云计算

TO B 新势力

Vue3-无限滚动的懒加载-本地数据操作版

Sam9029

Vue 前端 懒加载 9月月更

Vue3-无限滚动的懒加载-模拟网络请求Mock版

Sam9029

Vue 前端 9月月更 无限滚动

Spring事务角色与事务属性

十八岁讨厌编程

Java 后端开发 9月月更

数字化转型趋势|汽车电子软件开发工具链必看

laofo

DevOps cicd 研发效能 持续交付 DevOps工具链

C++学习---__gen_tempname函数原理分析学习

桑榆

c++ 源码分析 9月月更

Python语法之流程控制

向阳逐梦

流程控制 9月月更 嵌套

FaissPQ索引简介

转转技术团队

深度学习 Faiss 向量计算

2022-09-22:以下go语言代码输出什么?A:5、B:不能编译;C:运行时死锁。 package main import ( “fmt“ “time“ ) func main

福大大架构师每日一题

golang 福大大 选择题

【数据结构】利用Python手把手带你自定义矩阵

迷彩

数据结构 矩阵 矩阵运算 9月月更 自定义矩阵

经久不衰的设计定律是不要让我思考的设计

宇宙之一粟

读书笔记 设计 设计思维 设计原则 9月月更

客户说你的 SaaS 产品不好用怎么办?

产品海豚湾

产品经理 SaaS 客户关系管理 产品运营 9月月更

Linux内核详细笔记目录

Linux爱好者

嵌入式 Linux内核 驱动

【Kafka】编译 Kafka 源码并搭建源码环

石臻臻的杂货铺

kafka 九月月更

易观千帆联合《扬子晚报》发布苏州市手机银行应用活跃人数榜单

易观分析

金融 手机银行 苏州

Docker镜像列表中的none:none是什么

程序员欣宸

Docker 9月月更

Dubbo源码解析之SPI(一):扩展类的加载过程(下)_新基建_郑祥斌_InfoQ精选文章