这篇文章主要介绍“Dubbo怎么实现扩展机制”,在日常操作中,相信很多人在Dubbo怎么实现扩展机制问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Dubbo怎么实现扩展机制”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
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 的核心实现在 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,这是要创建扩展点了,代码有点长,但是我都做了相应的注释,包括绿色的注释。
逻辑还是很简单的,详细的代码没有具体展示,我先口述一下。
通过接口类名去三个目录找到对应的文件。
解析文件内容生成 class 对象,然后缓存到 cachedClasses 中。
然后通过 name 去 cachedClasses 中找到对应的 class 对象。
去缓存 EXTENSION_INSTANCES 看看是否已经实例化过了。
没有的话就实例化,然后调用 injectExtension 实现自动注入。
再通过 cachedWrapperClasses 实现包装,将最后的包装类返回。
有几点不清晰没关系,咱们接着分析,脑海中先大概知道要做什么,然后再来看看具体是怎么做的。
源码中的 loadDirectory 就是去目录找文件,然后解析,最终会调用 loadClass,这个方法很关键,我们详细分析一下,为了便于观看,删除了一些代码。
自适应咱们先略过,只要知道是在这里记录的即可。
然后上面提到的 AOP 相关的 cachedWrapperClasses 就是在这里记录的,如果判断它是包装类呢?
简单粗暴但是有效,只要有当前类作为构造器参数的类就是包装类,有点拗口,多读几遍就理解了。
现在我们再回过头来看看这段代码,Dubbo 的 AOP。
把扩展类对应的包装类都记录下来放在 cachedWrapperClasses 中,然后在实例化扩展类的时候就一层一层的把扩展类包起来,最终返回的就是包装类。
为什么说这就是 AOP 呢?因为等于把一些逻辑切进了扩展实现类中。
其实就是把扩展对象的公共逻辑移到包装类中,我们看下单元测试的例子就很清晰了。
从图中可以看到有两个扩展实现类,两个包装类,具体逻辑就不看了,不是重点,配置文件如下:
然后再看一下单元测试的运行结果,可以看到最终返回的其实是 Ext5Wrapper1 对象,并且它还包着 wrapper2 对象。
所以 echo 方法的调用链就是:Ext5Wrapper1 ->Ext5Wrapper2->Ext5impl1
也就起到了 AOP 的效果。
接下来我们再来看看 injectExtension,是如何实现 Dubbo 的自动注入。
到此,关于“Dubbo怎么实现扩展机制”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。