Lazy loaded image
🗒️SPI
Words 1800Read Time 5 min
2025-8-26
2025-8-28
type
status
date
slug
summary
tags
category
icon
password
原文
SPI(Service Provider Interface) 服务提供者的接口。JDK 内置的一种服务提供发现机制,允许程序在外部动态制定具体实现。
主要是被框架开发人员使用的一种技术。

术语

Service : 一个公开的接口或抽象类,定义了一个抽象的功能模块.
Service Provider: Service 接口的一个实现类.
ServiceLoader: SPI机制的核心组件,负责在运行时发现并加载 Service Provider.

Java SPI运行流程

notion image
 
 
调用方除了提供调用方实现的接口或抽象类,还要使用加载器,去加载实现方的 Class。如果有特殊的加载需求则可以使用自定义加载器。JDK提供了一个默认的ServiceLoader(java.util.ServiceLoader)。
 
实现方(Service Provider)实现该接口或者抽象类,编写具体的代码逻辑。Service Provider 须在 META-INF/service 提供实现的描述信息,且以 jar 的方式提供给调用方。调用方会通过该描述文件来加载该 class。
 
 

SPI与双亲委派机制

SPI机制也解决了 Java 类加载体系中的双亲委派机制。
以 JDBC 为例,JDK 提供了 Driver(java.sql.Driver) 接口,交由数据库驱动厂商自行去实现。Driver接口位于 rt.jar中,由启动类加载器(BootStrapClassLoader)加载。具体的 JDBC驱动实现如 com.mysql.cj.jdbc.Driver 是第三方jar,由应用加载器(AppClassLoader)加载,由于“双亲委派的限制”,父类加载器加载的类无法访问子类加载器加载的类,所以SPI 通过上下文类加载器饶过该限制。本质是理应由BootStrap类加载的 Class 反向委托给子类去加载。这样在编写JDBC时,开发者只需要将驱动 JAR 包放在类路径下,SPI 机制就能在运行时动态发现并加载这些实现。

SPI设计

SPI 是基于面向接口编程,能够优雅的实现模块之间的解耦。
通过面向接口、配置文件、反射技术来达到解耦合的效果。
应用场景:JDBC SLFJ……
 

API&SPI

广义上来说他们都属于接口
API(Application Programming Interface): 当实现方提供了接口和实现,通过实现方提供的接口拥有实现的能力
SPI(Service Provider Interface): 调用方提供了接口,由调用方确定了接口规则,实现方需针对该接口进行实现从而提供服务
 
notion image
 

Java SPI & SpringBoot 自动配置

  • SpringBoot 可以基于引入的依赖 JAR包实现自动装配
  • 提供了自动配置功能的依赖 JAR ,通常称之为 starter,例如 mybatis-spring-boot-starter 等等。
 
notion image
 

Dubbo SPI

JDK SPI的限制
  • 在查找扩展实现类的过程中,需要遍历SPI 配置文件中定义的所有实现类,该过程中会将这些实现类全部实例化。
  • 如果 SPI 配置文件中定义了多个实现类,只需要使用其中一个实现类时,只需要使用其中一个实现类时,就会生成不必要的对象。
当然这里说的 SPI 限制是标准的 SPI 实现,通过自定义 ServiceLoader 逻辑可以规避。
 
dubbo框架也定制了SPI,该机制是属于 Dubbo 的自动装配机制。
dubbo 作为一个 RPC 框架,它对扩展性有要求,例如RPC 注册中心可以使用 nacos 或者 zookeeper,通过 SPI 机制可以实现按需加载,实现任意替换组件,这极大提高了扩展性。
dubbo SPI 支持适配器模式,通过@Adaptive 注解来实现自适应扩展。

配置文件分类

按照配置文件的用途,分为三类目录。
  • META/services/ : 该目录下的 SPI 配置文件用来兼容 JDK SPI
  • META/dubbo/ : 该目录下用于存放用户自定义 SPI 配置文件
  • META/dubbo/internal/ : 该目录用于存放 dubbo 内部使用的 SPI 配置文件。
dubbo将SPI配置文件改成了 KV 格式

dubbo 示例

SPI注解标识该接口为一个扩展接口。其中value 表示默认的扩展名

Adaptive注解

dubbo SPI 支持自适应扩展。
通过 URL参数获取对应的值,来实现动态扩展。
Consumer通过扩展器加载该接口时,会自动为该接口自动生成一个自适应类 例如类似AdaptiveMessageService$Adaptive)。
 

ExtensionLoader

dubbo-common模块中的ExtensionLoader 类是 实现 dubbo SPI的核心类,它会去加载对应目录的配置文件,然后将创建对应的实例放入缓存当中。

加载配置文件

LoadingStrategy 描述加载策略接口,有三个实现类,描述了三种不同的加载策略
主要的作用是加载 META-INF/dubbo/internal/ 、 META-INF/services/ META-INF/dubbo 目录下的文件// -
1. DubboLoadingStrategy 用于加载用户自定义的 META-INF/dubbo 文件。
2. ServicesLoadingStrategy 加载 META-INF/services/目录下的配置文件,用于处理标准 Java SPI机制服务的加载。
3. DubboInternalLoadingStrategy 内置的加载策略 用于加载 dubbo 内置的扩展,位于 dubbo-common 模块下的META-INF/dubbo/internal/目录下的配置。

Adaptive注解

dubbo SPI 支持自适应扩展。
通过 URL参数获取对应的值,来实现动态扩展。
Consumer通过扩展器加载该接口时,会自动为该接口自动生成一个自适应类 例如类似AdaptiveMessageService$Adaptive)。
 

ExtensionLoader

dubbo-common模块中的ExtensionLoader 类是 实现 dubbo SPI的核心类,它会去加载对应目录的配置文件,然后将创建对应的实例放入缓存当中。

加载配置文件

LoadingStrategy 加载策略
LoadingStrategy为接口 有三个实现类,描述了三种不同的加载策略
主要的作用是加载 META-INF/dubbo/internal/ 、 META-INF/services/ META-INF/dubbo 目录下的文件// -
1. DubboLoadingStrategy 用于加载用户自定义的 META-INF/dubbo 文件
2. ServicesLoadingStrategy
3. DubboInternalLoadingStrategy 内置的加载策略 用于加载 dubbo 内置的扩展,位于 dubbo-common 模块下的META-INF/dubbo/internal/目录下的配置
 
上一篇
MySQL、Redis缓存一致性问题
下一篇
dubbo

Comments
Loading...