Service Provider Interface (SPI)

SPI 是一种基于“接口 + 策略模式 + 配置文件”组合实现的动态加载机制

它的主要应用场景是可替换的插件机制,让框架开发者能提供一组接口,而用户或第三方可以在不修改框架源码的情况下实现和扩展功能

SPI 基本流程

  1. 定义一组接口(框架开发者定义)
  2. 编写接口实现类(服务提供者实现)
  3. 配置 META-INF/services 文件
    • 文件名:接口全限定名
    • 文件内容:要加载的实现类的全限定名
    • 位置:src/main/resources/META-INF/services/
  4. 使用 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 的缺点

  1. 一次性加载所有实现

    没有延迟加载,未使用的实现也会被实例化,浪费性能

  2. 加载失败信息不透明

    比如 ScriptEngine,如果依赖缺失(如缺少 jruby.jar),会导致实现加载失败,但错误被屏蔽,调用方只能得到“不支持”的模糊提示

  3. 缺少 IoC 和 AOP 支持

    JDK SPI 无法在扩展点中自动注入其它依赖扩展,更无法结合 AOP 做动态增强

Dubbo SPI 的增强

Dubbo 在 JDK SPI 基础上,提供了更灵活和强大的扩展机制:

  • 配置方式

    • 文件位置:META-INF/dubbo/接口全限定名

    • 文件内容:

      名称1=扩展实现类1
      名称2=扩展实现类2
    • 每个实现类用换行分隔,Dubbo 会扫描整个 ClassPath

    增强特性

    1. 按需加载,避免浪费资源
    2. 失败信息透明,报错更清晰
    3. 支持 IoC(依赖注入)和 AOP(包装扩展)
    4. 支持 Adaptive 动态适配机制
    5. 支持 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

关键源码逻辑

  1. 获取 ExtensionLoader

    ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(T.class);
  2. 获取自适应实例(Adaptive Extension)

    内部会扫描所有实现类,生成适配器类,注入依赖

  3. 按名称获取扩展点

    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 的思路