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 这时候就派上用场了。

其实一点都不神奇,就是约定一个地方,加载的时候就去那个地方找实现类。

约定一个地方直白点说就是代码里面写死了一个目录,这个目录就是 META-INF/services/

然后在这个目录下创建一个文件,用接口全限定名来命名,文件内容就是实现类的全限定名。

到时候要实现类就根据接口名来这里找,然后实例化就行了。

挺简单的吧,这就是 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 来看看它测试用例怎么写的。

当然除了通过文件夹来找,直接用文件名搜也行。

找到了就好办了,数据都是造好的,找到你想要调试的方法,断点一设,箭头一点,这不就美滋滋了吗?

好了,小技巧分享完毕,回到 ExtensionLoader,我们简单点就用 Dubbo 单元测试的数据来看看实现。

有个叫 SimpleExt 的类,有三个实现,默认的实现是 impl1。

再来看看 SPI 配置文件的内容,可以看到为了测试还故意写了一些空格在配置文件中。

然后现在如果要找 impl2 这个实现,通过以下代码调用即可。

SimpleExt ext = ExtensionLoader
.getExtensionLoader(SimpleExt.class).getExtension("impl2")

一个扩展接口对应有个 ExtensionLoader,找到对应的 ExtensionLoader,然后再加载对应名字的实现类。

接下来会有源码,不过没关系,还是很简单的,想要深入源码这关必须过。

可以看到getExtensionLoader 是静态的,里面逻辑也很简单就是从缓存找接口对应的 ExtensionLoader,找不到就新建一个返回。

现在有了 ExtensionLoader,咱们再来看看 getExtension 的逻辑,来看看是如何通过扩展点 name 找到对应的实现类的。

可以看到又是有个缓存操作,逻辑非常简单,先去缓存找实例,如果没有则创建实例。

要说细节就是用到了双检锁,然后用 holder 来保证可见性和防止指令重排。应该看到注释上的 holder 构造了吧,volatile 和双检锁的搭配,这里就不深入了。

我们来看看 createExtension,这是要创建扩展点了,代码有点长,但是我都做了相应的注释,包括绿色的注释。

逻辑还是很简单的,详细的代码没有具体展示,我先口述一下。

  1. 通过接口类名去三个目录找到对应的文件。
  2. 解析文件内容生成 class 对象,然后缓存到 cachedClasses 中。
  3. 然后通过 name 去 cachedClasses 中找到对应的 class 对象。
  4. 去缓存 EXTENSION_INSTANCES 看看是否已经实例化过了。
  5. 没有的话就实例化,然后调用 injectExtension 实现自动注入。
  6. 再通过 cachedWrapperClasses 实现包装,将最后的包装类返回。

有几点不清晰没关系,咱们接着分析,脑海中先大概知道要做什么,然后再来看看具体是怎么做的。

源码中的 loadDirectory 就是去目录找文件,然后解析,最终会调用 loadClass,这个方法很关键,我们详细分析一下,为了便于观看,删除了一些代码。

自适应咱们先略过,只要知道是在这里记录的即可。

然后上面提到的 AOP 相关的 cachedWrapperClasses 就是在这里记录的,如果判断它是包装类呢?

简单粗暴但是有效,只要有当前类作为构造器参数的类就是包装类,有点拗口,多读几遍就理解了。

现在我们再回过头来看看这段代码,Dubbo 的 AOP。

把扩展类对应的包装类都记录下来放在 cachedWrapperClasses 中,然后在实例化扩展类的时候就一层一层的把扩展类包起来,最终返回的就是包装类。

为什么说这就是 AOP 呢?因为等于把一些逻辑切进了扩展实现类中。

其实就是把扩展对象的公共逻辑移到包装类中,我们看下单元测试的例子就很清晰了。

从图中可以看到有两个扩展实现类,两个包装类,具体逻辑就不看了,不是重点,配置文件如下:

然后再看一下单元测试的运行结果,可以看到最终返回的其实是 Ext5Wrapper1 对象,并且它还包着 wrapper2 对象。

所以 echo 方法的调用链就是:

Ext5Wrapper1 ->Ext5Wrapper2->Ext5impl1

也就起到了 AOP 的效果。

接下来我们再来看看 injectExtension,是如何实现 Dubbo 的自动注入。

看了代码之后是不是有点失望,就这?

是的就是这么朴素地判断有没有 set 方法,然后根据参数找到对象,执行 set 方法注入即可。

所以说源码之下无秘密,看起来好像很高级的东西,就这。

上面代码中还有个objectFactory.getExtension(),这个和扩展自适应有关系,还有个@Activate也没说。

这些内容还是有点多的,也很重要,感觉上可能还有点绕,所以单独写一篇说。

最后

Dubbo 就是靠自己实现的 SPI 机制把通信协议、序列化格式、负载均衡、路由策略等各部分抽出来作为插件,实现扩展和定制。

通过微内核和SPI 机制来满足用户定制化的需求,也保证了框架本身的稳定性和可持续性。

并且 Dubbo 自身也提供了很多已有的实现,像各种路由策略等等。

所以说一个好的框架不仅自己功能要全,还得对扩展开放,这样生态才会壮大。

今天的代码还是有点多的,如果看不懂的建议下载源码,跟着源码调试几遍就清晰了。

源码这一步一定要迈过去,迈过去了之后就轻松了。

Dubbo 系列持续更新,敬请期待,有问题可以留言,我会尽量解答。

欢迎关注我的个人公众号,文章首发公众号。

更多文章可看我的文章汇总:https://github.com/yessimida/yes 欢迎 star !


我是 yes,从一点点到亿点点,欢迎在看、转发、留言,我们下篇见。

Dubbo 就是靠它崭露头角!(身为开源框架很重要的一点)的更多相关文章

  1. Pyhton开源框架(加强版)

    info:Djangourl:https://www.oschina.net/p/djangodetail: Django 是 Python 编程语言驱动的一个开源模型-视图-控制器(MVC)风格的 ...

  2. Python开源框架

    info:更多Django信息url:https://www.oschina.net/p/djangodetail: Django 是 Python 编程语言驱动的一个开源模型-视图-控制器(MVC) ...

  3. Everything is Serverless,从开源框架对比说起

    摘要:Everything is Serverless. 在众多云计算解决方案中,Serverless 逐渐崭露头角,受到了很多关注并且发展迅猛,今天就关于serverless 开源框架细说二三. 什 ...

  4. 开源框架TLog核心原理架构解析

    前言 最近在做TLog 1.2.5版本的迭代,许多小伙伴之前也表示说很想参与开源项目的贡献.为了让项目更好更快速的迭代新特性以及本着发扬开源精神互相学习交流,很有幸招募到了很多小伙伴与我一起前行. 为 ...

  5. Android 开源框架Universal-Image-Loader学习

    Android 开源框架Universal-Image-Loader完全解析(一)--- 基本介绍及使用 Android 开源框架Universal-Image-Loader完全解析(二)--- 图片 ...

  6. iOS常用第三方开源框架和优秀开发者博客等

    博客收藏iOS开发过程好的开源框架.开源项目.Xcode工具插件.Mac软件.文章等,会不断更新维护,希望对你们有帮助.如果有推荐或者建议,请到此处提交推荐或者联系我. 该文档已提交GitHub,点击 ...

  7. 值得推荐的android开源框架

    1.volley 项目地址https://github.com/smanikandan14/Volley-demo (1) JSON,图像等的异步下载: (2) 网络请求的排序(scheduling) ...

  8. 介绍一个非常好用的跨平台C++开源框架:openFrameworks

    介绍一个非常好用的跨平台C++开源框架:openFrameworks 简介 首先需要说明的一点是: openFrameworks 设计的初衷不是为计算机专业人士准备的, 而是为艺术专业人士准备的, 就 ...

  9. iOS超全开源框架、项目和学习资料汇总(5)AppleWatch、经典博客、三方开源总结篇

    完整项目 v2ex – v2ex 的客户端,新闻.论坛.apps-ios-wikipedia – apps-ios-wikipedia 客户端.jetstream-ios – 一款 Uber 的 MV ...

随机推荐

  1. 数组编程题(github每日一题)

    /** * 随机生成一个长度为 10 的整数类型的数组,例如 [2, 10, 3, 4, 5, 11, 10, 11, 20], * 将其排列成一个新数组,要求新数组形式如下,例如 [[2, 3, 4 ...

  2. 「IOI2017」接线 的另类做法

    看到这题,我的第一反应是:这就是一个费用流模型?用模拟费用流的方法? 这应该是可以的,但是我忘记了怎么模拟费用流了IOI不可能考模拟费用流.于是我就想了另外一个方法. 首先我们考虑模拟费用流的模型如下 ...

  3. centos7网卡bond配置--自己另一篇文章的补充

    这篇文章是自己另一篇文章的第二种方法的一个完善的补充 https://www.cnblogs.com/zzf0305/p/9588585.html 1 备份网卡配置文件2 使用nmcli命令配置bon ...

  4. MVC错误页面相关说明

    1.如果使用普通的纯静态页面,在httpErrors中配置的话,返回的status code是200,不会是对应的错误码404.只能使用,aspx或mvc页面,加入,这样就会返回的时候就会显示404错 ...

  5. Docker 安装 Redis 需要注意的地方

    Docker 安装 Redis 需要注意的地方 拉取镜像 docker pull redis 可以使用redis:xxx xxx为版本号,不写默认是latest 启动容器 无配置文件无密码: dock ...

  6. GaussDB(DWS)应用实践丨负载管理与作业排队处理方法

    摘要:本文用来总结一些GaussDB(DWS)在实际应用过程中,可能出现的各种作业排队的情况,以及出现排队时,我们应该怎么去判断是否正常,调整一些参数,让资源分配与负载管理更符合当前的业务:或者在作业 ...

  7. thinkphp thinkphp6 安装JWT

    第一步:composer安装   composer require firebase/php-jwt 下图是执行成功 cd 进入项目目录的vendor 找到firebase 看到下面有一个php-jw ...

  8. 百度网站统计和CNZZ网站统计对比

    一,前言 百度统计和cnzz统计是目前市面上比较流行的两种web统计工具,接下来将对两个统计工具做初步的体验测评 百度网站统计相关介绍:全球最大的中文网站流量分析平台,帮助企业收集网站访问数据,提供流 ...

  9. 工具-chrome相关-安装crx包及错误解决(99.3.1)

    @ 目录 1.安装教程 2.程序包无效:"CRX_HEADER_INVALID" 1.安装教程 在浏览器上输入 chrome://extensions 并且选择开发者模式 将.cr ...

  10. js 获取某月第一天和最后一天

    1.获取某月第一天和最后一天日期 function getDateByMonth (timeStamp) { let inDate = new Date(timeStamp) let year = i ...