Hola,我是 yes。
经过了 RPC 核心和 Dubbo 微内核两篇文章后,今天终于要稍稍深入一波 Dubbo 了。
作为一个通用的 RPC 框架,性能是很重要的一环,而易用性和扩展性也极为重要。
简单地、无侵入式地扩展和定制 RPC 各阶段功能是很多团队的述求,Dubbo 就满足了这些需求。
它通过微内核设计和 SPI 扩展,使得一些有特殊需求的业务团队可以在 Dubbo 中实现自己的扩展,而不需要修改源码。
Dubbo 的成功离不开这样的设计,今天咱们就来盘一盘 Dubbo 是如何实现无侵入扩展的,其间还会看到 Dubbo 的 IOC 和 AOP。
还有先打个预防针,今天的内容代码有点多的,毕竟想要深入剖析,源码必不可少,刚好也顺带提一下看源码的小技巧。
所以建议电脑上看,更加清晰和舒适。
还有如果有没看过源码的同学,来紧跟 Dubbo 这个系列吧,到时候再也不怕被面试官问看过源码没了。
SPI
Dubbo 就是利用 SPI (Service Provider Interface)来实现扩展机制的。
这个 SPI 想必你们都很熟悉,在大学写数据库大作业的时候就碰到了,访问数据库需要用到 java.sql.Driver
。
市面上的数据库五花八门,每个数据库厂商都有自己的实现,所以肯定需要定制一个接口,这样我们面向接口编程即可。
而具体的实现则可以通过配置来加载,JDK SPI 这时候就派上用场了。
其实一点都不神奇,就是约定一个地方,加载的时候就去那个地方找实现类。
到时候要实现类就根据接口名来这里找,然后实例化就行了。
挺简单的吧,这就是 JDK SPI,但是它不满足 Dubbo 的需求。
因为 Dubbo 把自身的一些实现也剥离出来成为扩展,而这些实现还是有点多的,也不需要全部用上。
如果用 JDK SPI 会把配置文件里面的类全部加载,这就导致资源的浪费。用的时候还需要遍历过去才能找到对应的实现。
所以 Dubbo 就在 JDK SPI 的基础上实现了个 Dubbo 的 SPI,可以根据指定的名称按需加载实现类,比如拿 Cluster 来说就有这么多实现类。
约定的地方改了一下,一共有三个目录。
- META-INF/dubbo/internal/ :这里是存放 Dubbo 内部使用的 SPI 配置文件。
- META-INF/dubbo/ :这里是存放用户自定义 SPI 配置文件。
- META-INF/services/:兼容 JDK SPI
然后文件里面的内容是key=value
形式,这样就可以根据 key 找到对应的实现类。
然后在注解上可以配置默认的 key 来选择默认的实现类,比如 Cluster 默认的实现是 failover。
也可以通过 URL 参数来选择实现类。
还有像 JDK SPI 扩展点加载失败的话,连扩展点名称都拿不到,到时候报错也不知道哪里出问题。
而 Dubbo SPI 则不会吃了错误,并且还提供了扩展点的自动注入和 AOP 功能。
大致了解了 Dubbo SPI 之后,我们再来深入看看实现细节。
Dubbo SPI 实现细节
Dubbo SPI 的核心实现在 ExtensionLoader 中,它负责扩展点的加载和生命周期的维护,类似 JDK SPI 的 ServiceLoader。
这里要先提一点看源码的小技巧了。
开源框架都会有单元测试,而单元测试里面就会有我们看源码时候想要的各种功能实现,我们就可以从单元测试入手得知一些功能的划分,然后断点调试逐渐深入。
比如今天文章的 ExtensionLoader ,它在 dubbo-common 模块中,咱们就进入 test 来看看它测试用例怎么写的。
当然除了通过文件夹来找,直接用文件名搜也行。
找到了就好办了,数据都是造好的,找到你想要调试的方法,断点一设,箭头一点,这不就美滋滋了吗?
然后现在如果要找 impl2 这个实现,通过以下代码调用即可。
SimpleExt ext = ExtensionLoader .getExtensionLoader(SimpleExt.class).getExtension("impl2")
一个扩展接口对应有个 ExtensionLoader,找到对应的 ExtensionLoader,然后再加载对应名字的实现类。
接下来会有源码,不过没关系,还是很简单的,想要深入源码这关必须过。