您好!
欢迎来到京东云开发者社区
登录
首页
博文
课程
大赛
工具
用户中心
开源
首页
博文
课程
大赛
工具
开源
更多
用户中心
开发者社区
>
博文
>
深入理解java和dubbo的SPI机制
分享
打开微信扫码分享
点击前往QQ分享
点击前往微博分享
点击复制链接
深入理解java和dubbo的SPI机制
自猿其说Tech
2022-07-18
IP归属:未知
206640浏览
计算机编程
#### 1 SPI简介 #### 1.1 SPI(Service Provider Interface) 本质:将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。 java SPI:用来设计给服务提供商做插件使用的。基于策略模式来实现动态加载的机制。我们在程序只定义一个接口,具体的实现交个不同的服务提供者;在程序启动的时候,读取配置文件,由配置确定要调用哪一个实现。 dubbo SPI:在dubbo中也有SPI机制,虽然都需要将接口全限定名配置在文件中,但是dubbo并没有使用java的spi机制,而是重新实现了一套功能更强的 SPI 机制, 支持了AOP与依赖注入,并且 利用缓存提高加载实现类的性能,同时 支持实现类的灵活获取。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。例如dubbo当中的protocol,LoadBalance等都是通过SPI机制扩展。 ### 2 java SPI #### 2.1实现过程 1)需要在 classpath 下创建一个目录,该目录命名必须是:META-INF/service 2)在该目录下创建一个 文本文件,该文件需要满足以下几个条件 - 文件名必须是扩展的接口的全路径名称 - 文件内部描述的是该扩展接口的所有实现类 - 文件的编码格式是 UTF-8 3)通过 java.util.ServiceLoader 的加载机制来加载服务 ![](//img1.jcloudcs.com/developer.jdcloud.com/1aab166d-8346-4e1a-ac1b-a81f85109b6520220718150148.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/1a5018ef-f407-4ac1-9890-d2a93745f23420220718150155.png) #### 2.2 工作原理 1)当调用 ServiceLoader.load(Class clz) 方法时,会到jar中中的目录 "META-INF/services/" + clz.getName 进行文件读取, 2)当在调用ServiceLoader.forEach()方法时,实际走的是LazyIterator,当在调用LazyIterator.hasNext() 时,在文件中读取到实际的服务实现类并把它们通过调用 Class.forName(String name, boolean initialize,ClassLoader loader)。 ![](//img1.jcloudcs.com/developer.jdcloud.com/db7361fc-6edb-4bc1-995c-c441d818c5b520220718150222.png) #### 2.3 实际应用 javaSPI我们最熟悉的应用就是数据库驱动了,mysql和oracle驱动针对JDBC分别有自己的实现,这就有赖于java的SPI机制。 ![](//img1.jcloudcs.com/developer.jdcloud.com/423ae24f-375d-4bbd-8254-644fc5f6c52620220718150234.png) ### 3 dubbo SPI #### 3.1 实现过程 1)需要在 classpath 下创建一个目录,该目录命名可以是:META-INF/service/、META-INF/dubbo/、META-INF/dubbo/internal/ 2)在该目录下创建一个 文本文件,该文件需要满足以下几个条件 - 文件名必须是扩展的接口的全路径名称 - 文件内部描述的是该扩展接口的所有实现类,将服务实现类写成KV键值对的形式,Key是拓展类的name,Value是扩展的全限定名实现类。 3)通过 org.apache.dubbo.common.extension.ExtensionLoader 的加载机制来加载服务 ![](//img1.jcloudcs.com/developer.jdcloud.com/e75629c5-5424-48f3-8270-49eca52a2b4d20220718150318.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/15eb17d7-caa9-4c35-871c-b4ee5307951c20220718150326.png) #### 3.2 工作原理 1)我们首先通过 ExtensionLoader的 getExtensionLoader 方法获取一个接口的 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象,源码如下,首先是 getExtensionLoader 方法: ![](//img1.jcloudcs.com/developer.jdcloud.com/ce51c87b-7d0f-4179-8be3-df76a544339320220718150351.png) new ExtensionLoader<T>(type)源码如下: ![](//img1.jcloudcs.com/developer.jdcloud.com/e5c9ed34-63a8-4fdc-9cf7-7c995126446a20220718150400.png) 注意这里创建 ExtensionLoader对象的构造方法如下:ExtensionLoader.getExtensionLoader获取ExtensionFactory接口的拓展类,再通过 getAdaptiveExtension从拓展类中获取目标拓展类。 2)通过 ExtensionLoader.getExtensionLoader取到接口的加载器Loader之后,再通过 getExtension方法获取需要拓展类对象。 ![](//img1.jcloudcs.com/developer.jdcloud.com/6861a89b-9952-4a5c-9cb6-3c07a776330d20220718150419.png) 以上代码首先检查holder中的实例缓存,缓存未命中则创建拓展对象。dubbo中包含了大量的扩展点缓存。这个就是典型的使用空间换时间的做法。 ![](//img1.jcloudcs.com/developer.jdcloud.com/335cb204-1117-421e-9a02-66382cb5649620220718150429.png) 创建拓展类对象步骤分别为: 1. 通过 getExtensionClasses 从配置文件中加载所有的拓展类,再通过名称获取目标拓展类 2. 通过反射创建拓展对象 3. 向拓展对象中注入依赖 4. 将拓展对象包裹在相应的 Wrapper 对象中 我们接下来重点看下getExtensionClasses方法: ![](//img1.jcloudcs.com/developer.jdcloud.com/4d2f4481-41d1-472d-b6c8-f22c92b89def20220718150502.png) 先从缓存中获取class,缓存未命中则调用loadExtensionClasses方法加载,我们再看下loadExtensionClasses这个方法: ![](//img1.jcloudcs.com/developer.jdcloud.com/add1b348-59fc-4426-95c3-219c74cb092520220718150517.png) 我们看到这里遍历调用了多个策略去加载class的,跟到这里我们发现非常有意思的是:dubbo在加载META-INF目录下的class键值对的时候采用了javaSPI的方式 ![](//img1.jcloudcs.com/developer.jdcloud.com/309b1dae-c943-4fb4-852c-d38b3df85f7420220718150528.png) ![](//img1.jcloudcs.com/developer.jdcloud.com/bf74d052-cbd9-415f-bd62-1a722d06337d20220718150537.png) 这里dubbo使用javaSPI的方式加载到3中类加载策略: org.apache.dubbo.common.extension.DubboInternalLoadingStrategy 用于加载META-INF/dubbo/internal/中的class org.apache.dubbo.common.extension.DubboLoadingStrategy 用于加载META-INF/dubbo/中的class org.apache.dubbo.common.extension.ServicesLoadingStrategy 用于加载META-INF/service/中的class dubbo的SPI还提供了自适应(Adaptive)、自动注入的功能就不在这里过多展开了,有兴趣可以自行了解。 #### 3.3 实际应用 dubbo中大量使用了SPI机制: ![](//img1.jcloudcs.com/developer.jdcloud.com/ac7899d7-e614-43f1-b2f8-1a6be798afa220220718150556.png) 例如dubbo的多协议的实现: ![](//img1.jcloudcs.com/developer.jdcloud.com/b1876c1d-4eb4-445a-9005-387978500d2d20220718150605.png) ### 4 javaSPI和dubboSPI对比 1. Java SPI在加载扩展点的时候,会一次性加载所有可用的扩展点,很多是不需要的,会浪费系统资源。dubboSPI有选择性地加载所需要的SPI接口。 2. javaSPI配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。而dubboSPI配置文件中以键值对的形式有别名,易于区分。 3. SPI扩展如果依赖其他的扩展,javaspi做不到自动注入和装配,dubbo可以实现自动注入。 4. javaSPI不提供类似于Spring的IOC和AOP功能,dubboSPI是支持的 ------------ ###### 自猿其说Tech-JDL京东物流技术与数据智能部 ###### 作者:龚航林
原创文章,需联系作者,授权转载
上一篇:【敏捷转型,效能提升】敏捷转型实践系列分享(之一)
下一篇:国际计费系统基于Sharding-Proxy大数据迁移方案实践
相关文章
Taro小程序跨端开发入门实战
Flutter For Web实践
配运基础数据缓存瘦身实践
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
本文将从Optional所解决的问题开始,逐层解剖,由浅入深,文中会出现Optioanl方法之间的对比,实践,误用情况分析,优缺点等。与大家一起,对这项Java8中的新特性,进行理解和深入。
01
Taro小程序跨端开发入门实战
为了让小程序开发更简单,更高效,我们采用 Taro 作为首选框架,我们将使用 Taro 的实践经验整理了出来,主要内容围绕着什么是 Taro,为什么用 Taro,以及 Taro 如何使用(正确使用的姿势),还有 Taro 背后的一些设计思想来进行展开,让大家能够对 Taro 有个完整的认识。
01
Flutter For Web实践
Flutter For Web 已经发布一年多时间,它的发布意味着我们可以真正地使用一套代码、一套资源部署整个大前端系统(包括:iOS、Android、Web)。渠道研发组经过一段时间的探索,使用Flutter For Web技术开发了移动端可视化编程平台—Flutter乐高,在这里希望和大家分享下使用Flutter For Web实践过程和踩坑实践
01
配运基础数据缓存瘦身实践
在基础数据的常规能力当中,数据的存取是最基础也是最重要的能力,为了整体提高数据的读取能力,缓存技术在基础数据的场景中得到了广泛的使用,下面会重点展示一下配运组近期针对数据缓存做的瘦身实践。
自猿其说Tech
文章数
426
阅读量
2149963
作者其他文章
01
深入JDK中的Optional
01
Taro小程序跨端开发入门实战
01
Flutter For Web实践
01
配运基础数据缓存瘦身实践
添加企业微信
获取1V1专业服务
扫码关注
京东云开发者公众号