深入理解java和dubbo的SPI机制

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 的加载机制来加载服务

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)。

2.3 实际应用

javaSPI我们最熟悉的应用就是数据库驱动了,mysql和oracle驱动针对JDBC分别有自己的实现,这就有赖于java的SPI机制。

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 的加载机制来加载服务

3.2 工作原理

1)我们首先通过 ExtensionLoader的 getExtensionLoader 方法获取一个接口的 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象,源码如下,首先是 getExtensionLoader 方法:

new ExtensionLoader(type)源码如下:

注意这里创建 ExtensionLoader对象的构造方法如下:ExtensionLoader.getExtensionLoader获取ExtensionFactory接口的拓展类,再通过 getAdaptiveExtension从拓展类中获取目标拓展类。

2)通过 ExtensionLoader.getExtensionLoader取到接口的加载器Loader之后,再通过 getExtension方法获取需要拓展类对象。

以上代码首先检查holder中的实例缓存,缓存未命中则创建拓展对象。dubbo中包含了大量的扩展点缓存。这个就是典型的使用空间换时间的做法。

创建拓展类对象步骤分别为:

  1. 通过 getExtensionClasses 从配置文件中加载所有的拓展类,再通过名称获取目标拓展类
  2. 通过反射创建拓展对象
  3. 向拓展对象中注入依赖
  4. 将拓展对象包裹在相应的 Wrapper 对象中

我们接下来重点看下getExtensionClasses方法:

先从缓存中获取class,缓存未命中则调用loadExtensionClasses方法加载,我们再看下loadExtensionClasses这个方法:

我们看到这里遍历调用了多个策略去加载class的,跟到这里我们发现非常有意思的是:dubbo在加载META-INF目录下的class键值对的时候采用了javaSPI的方式

这里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机制:

例如dubbo的多协议的实现:

4 javaSPI和dubboSPI对比

  1. Java SPI在加载扩展点的时候,会一次性加载所有可用的扩展点,很多是不需要的,会浪费系统资源。dubboSPI有选择性地加载所需要的SPI接口。
  2. javaSPI配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。而dubboSPI配置文件中以键值对的形式有别名,易于区分。
  3. SPI扩展如果依赖其他的扩展,javaspi做不到自动注入和装配,dubbo可以实现自动注入。
  4. javaSPI不提供类似于Spring的IOC和AOP功能,dubboSPI是支持的

作者:京东物流 龚航林

来源:京东云开发者社区 自猿其说Tech 转载请注明来源

相关推荐
不停喝水25 分钟前
【前端转全栈java速通课】 项目实战④7-11节 操作数据库-登录-注册-修改密码-注销-mubatis-plus简化crud-
java·前端·数据库
y1su30 分钟前
Leetcode 二分模板
java·数据结构·算法·leetcode·排序算法
霸道流氓气质1 小时前
多Agent通信机制与协议设计完全指南:从FIPA-ACL到A2A/MCP的Java生产级实战
java·开发语言
007张三丰1 小时前
C/C++ 内存管理详解:从内存分布到 new/delete 底层原理
java·c语言·c++·内存管理
骇客野人1 小时前
Java BIO / NIO / AIO 完整详解 + 编程技巧
java·开发语言·nio
霸道流氓气质1 小时前
OpenTelemetry 入门与实战:Java Agent、Spring Boot Starter与LLM调用追踪示例
java·开发语言·spring boot
m0_587383001 小时前
24 小时自助健身房系统软件开发实战指南与案例解析
java·spring boot·spring·系统架构·需求分析
栖凤1 小时前
Java 的 try-catch 在 Agent 里失效了,我用了 5 个模式才兜住
java·前端·python
小师兄吃牛肉1 小时前
Java 同步系列之 ZooKeeper 分布式锁
java·分布式·java-zookeeper