Service Provider Interface (SPI)
SPI 是一种基于“接口 + 策略模式 + 配置文件”组合实现的动态加载机制
它的主要应用场景是可替换的插件机制,让框架开发者能提供一组接口,而用户或第三方可以在不修改框架源码的情况下实现和扩展功能
SPI 基本流程
- 定义一组接口(框架开发者定义)
- 编写接口实现类(服务提供者实现)
- 配置 META-INF/services 文件
- 文件名:接口全限定名
- 文件内容:要加载的实现类的全限定名
- 位置:
src/main/resources/META-INF/services/
- 使用 ServiceLoader 加载实现
- 通过
ServiceLoader.load(接口.class)
读取配置,按需返回实现实例
- 通过
使用场景
JDBC 驱动加载
-
JDBC 4.0 之前 需要显式加载驱动类:
Class.forName("com.mysql.jdbc.Driver");
JDBC 4.0 之后 通过 SPI 自动发现驱动:
String url = "jdbc:mysql://localhost:3306/demo";
Connection conn = DriverManager.getConnection(url, username, password);
common-logging
META-INF/services/org.apache.commons.logging.LogFactory(是个抽象类)
Dubbo
Dubbo 对 JDK SPI 进行了增强,实现了更强大的扩展机制
JDK SPI 的缺点
-
一次性加载所有实现
没有延迟加载,未使用的实现也会被实例化,浪费性能
-
加载失败信息不透明
比如
ScriptEngine
,如果依赖缺失(如缺少jruby.jar
),会导致实现加载失败,但错误被屏蔽,调用方只能得到“不支持”的模糊提示 -
缺少 IoC 和 AOP 支持
JDK SPI 无法在扩展点中自动注入其它依赖扩展,更无法结合 AOP 做动态增强
Dubbo SPI 的增强
Dubbo 在 JDK SPI 基础上,提供了更灵活和强大的扩展机制:
-
配置方式
-
文件位置:
META-INF/dubbo/接口全限定名
-
文件内容:
名称1=扩展实现类1 名称2=扩展实现类2
-
每个实现类用换行分隔,Dubbo 会扫描整个 ClassPath
增强特性
- 按需加载,避免浪费资源
- 失败信息透明,报错更清晰
- 支持 IoC(依赖注入)和 AOP(包装扩展)
- 支持
Adaptive
动态适配机制 - 支持
Activate
条件激活扩展
JDK SPI 与 Dubbo SPI 对比
特性 JDK SPI Dubbo SPI 配置文件路径 META-INF/services/接口全限定名
META-INF/dubbo/接口全限定名
配置内容 只写实现类全限定名(逐行) 别名=实现类全限定名
,支持多实现加载机制 一次性实例化所有实现类 按需加载,延迟创建实例 错误处理 失败原因被屏蔽,难以定位 明确抛出异常,便于调试 依赖注入 (IoC) 不支持 支持 setter 注入其它扩展 AOP 支持 不支持 支持 Wrapper 包装扩展 动态适配 (Adaptive) 不支持 支持,根据运行时参数选择实现 条件激活 (Activate) 不支持 支持,根据 URL/group 等条件自动激活扩展 典型应用 JDBC Driver, Commons Logging Dubbo RPC 框架,插件化扩展 Dubbo SPI 运行流程
SequenceDiagram(源码内部流程)
-
sequenceDiagram participant ExtensionLoader participant LoopWrapperClass as loop wrapper class ExtensionLoader->>ExtensionLoader: 1 getExtensionLoader Note right of ExtensionLoader: 参数为SPI接口 ExtensionLoader->>ExtensionLoader: 2 getAdaptiveExtension Note right of ExtensionLoader: 获取适配器实例 ExtensionLoader->>ExtensionLoader: 3 createAdaptiveExtension Note right of ExtensionLoader: 创建适配器类 ExtensionLoader->>ExtensionLoader: 4 getAdaptiveExtensionClass Note right of ExtensionLoader: 获取SPI的所有扩展的Class对象 ExtensionLoader->>ExtensionLoader: 5 getExtensionClass Note right of ExtensionLoader: 获取SPI的所有扩展的Class对象 ExtensionLoader->>ExtensionLoader: 6 createAdaptiveExtensionClass Note right of ExtensionLoader: 真正创建适配器类的Class对象 ExtensionLoader->>ExtensionLoader: 7 injectExtension Note right of ExtensionLoader: 注入适配器类依赖的其他扩展 ExtensionLoader->>ExtensionLoader: 8 getExtension Note right of ExtensionLoader: 参数为一个字符串,返回使用wrap包装后的扩展点 ExtensionLoader->>ExtensionLoader: 9 createExtension Note right of ExtensionLoader: 创建扩展 ExtensionLoader->>LoopWrapperClass: 10 injectExtension Note right of LoopWrapperClass: 注入wrap到扩展点
Flowchart(调用方视角)
%%{init: {"flowchart": {"wrappingWidth": "1000"}}}%% flowchart TD %% 调用层 A["Client 调用 getExtension('xxx')"] %% 缓存层 B{"缓存中有实例?"} C[返回缓存实例] %% SPI 加载层 subgraph SPI["SPI 加载"] D[加载 SPI 配置文件 META-INF/dubbo/...] E[解析 class 名称列表] F[遍历每个 class] --> G[加载 Class] end %% 类型判断与实例化层 subgraph 类型实例化["类型判断 & 实例化"] style I fill:#dff0d8,stroke:#3c763d,color:#3c763d I[Adaptive 实例化] style J fill:#f2dede,stroke:#a94442,color:#a94442 J[Wrapper 实例化] style K fill:#ffffff,stroke:#666,color:#666 K[普通扩展实例化] %% 缓存 L1[缓存实例] L2[缓存实例] L3[缓存实例] end %% Wrapper 包装层 subgraph Wrapper["Wrapper 包装"] N[用 Wrapper 包装实例] end %% Activate 激活层 subgraph Activate["Activate 激活"] style O fill:#f5f5f5,stroke:#333,color:#333 O{Activate 规则?} P[根据 group/URL 过滤激活扩展] Q[跳过过滤] end %% 返回层 R[返回最终扩展实例给调用方] %% 流程连接 A --> B B -- 是 --> C --> R B -- 否 --> D --> E --> F F --> I --> L2 F --> J --> L3 --> N F --> K --> L1 L1 -.-> O L2 -.-> O N -.-> O O -- 是 --> P --> R O -- 否 --> Q --> R
关键源码逻辑
-
获取 ExtensionLoader
ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(T.class);
-
获取自适应实例(Adaptive Extension)
内部会扫描所有实现类,生成适配器类,注入依赖
-
按名称获取扩展点
loader.getExtension("xxx")
返回时会经过 Wrapper 包装,最终注入到调用方
JDK 自带的 SPI 本地化扩展
JDK 也内置了部分基于 SPI 的本地化支持,通常需要打包成 jar 并放到 jre/lib/ext
下,例如:
CalendarDataProvider
CalendarNameProvider
CurrencyNameProvider
LocaleNameProvider
LocaleServiceProvider
ResourceBundleControlProvider
TimeZoneNameProvider
总结
-
JDK SPI
简单、通用,但功能有限(一次性加载、错误隐藏、缺少 IoC/AOP)
-
Dubbo SPI
在 JDK SPI 基础上做了增强,支持按需加载、依赖注入、Wrapper 包装、条件激活和动态适配
-
使用建议
如果是通用 Java 应用,
ServiceLoader
足够;如果是高扩展性框架(RPC、插件化系统),建议借鉴 Dubbo SPI 的思路