Java 与 Dubbo 的 SPI 机制介绍

Java SPI 机制

概念介绍

Java 原生支持 SPI 机制,具体介绍如下:

  • 核心概念
    • SPI(Service Provider Interface)是一种服务发现机制。
    • SPI 的核心思想是定义一个接口,由多个实现类提供不同的实现方式,在系统运行时根据配置或者默认策略,动态加载并使用具体的实现类。
    • SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样就可以在运行时,动态为接口替换实现类。
  • 工作原理

    • (1) 接口与实现类
      • 假设有一个接口 A,它有多个实现类:A -> A1、A2、A3
    • (2) 配置实现类
      • 可以在配置文件中指定接口 A 对应使用哪个实现类。
    • (3) 运行时加载
      • 程序启动时,会读取配置文件,根据配置信息找到对应的实现类,实例化并使用该对象。
    • (4) Java 原生 SPI 机制
      • Java 原生 SPI 机制的使用要求:
        • resources/META-INF/services/ 目录下,创建一个与接口全限定名相同的文件,例如:resources/META-INF/services/com.example.service.A
        • 文件内容的格式是一行一个实现类的全限定名(可以有多行,即支持多个不同的实现类),例如:com.example.service.impl.A1
        • 运行时通过 ServiceLoader 等工具扫描依赖的 Jar 包,在其中查找该文件,并加载指定的实现类,比如:ServiceLoader<HelloService> loader = ServiceLoader.load(HelloService.class);
  • 应用场景

    • SPI 机制常用于插件式扩展。
    • 比如:如果你在开发一个框架,可以通过 SPI 让外部开发者编写插件,扩展框架的功能,而不必修改框架的源码。
  • 典型案例

    • JDBC
      • Java 标准库只定义了一套 JDBC 接口,并没有真正的实现。
      • 数据库厂商(如 MySQL、Oracle)会提供自己的实现,并通过 SPI 机制声明在 resources/META-INF/services/ 目录中。
      • 运行时,Java 会根据项目引入的数据库驱动 Jar 包,自动找到对应的 JDBC 实现类。

Dubbo SPI 机制

概念介绍

Dubbo 借鉴了 SPI 思想,但没有直接使用 Java 原生的 SPI 机制,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader 类可以加载指定的实现类。

  • 工作原理

    • (1) 接口声明
      • 在 Dubbo 中,如果某个接口需要支持 SPI 扩展,就会加上 @SPI 注解,比如 Protocol 接口:
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        // 接口定义
        @SPI("dubbo")
        public interface Protocol {

        int getDefaultPort();

        @Adaptive
        <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;

        @Adaptive
        <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;

        void destroy();
        }

        // 加载实现类
        Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
      • @SPI("dubbo") 表示默认实现是 dubbo
      • @Adaptive 表示该方法会生成代理逻辑,运行时根据参数动态选择实现类。
    • (2) 实现类配置
      • Dubbo 在自己 Jar 包中的 resources/META-INF/dubbo/internal/ 路径下提供了一个配置文件,文件名是接口的全限定名,比如:
        1
        resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
      • 配置文件的内容是 key=实现类的全限定名,key 对应 @SPI 注解中的扩展名称,比如:
        1
        2
        3
        dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
        http=com.alibaba.dubbo.rpc.protocol.http.HttpProtocol
        hessian=com.alibaba.dubbo.rpc.protocol.hessian.HessianProtocol
    • (3) 默认实现加载
      • 如果用户没有配置扩展,Dubbo 会根据 @SPI("dubbo") 的默认值 dubbo,从配置文件中加载对应的实现类 DubboProtocol
      • 这也是 Dubbo 默认使用 Dubbo 协议作为 RPC 通信协议的原因。
    • (4) 动态切换实现类
      • Protocol 接口中,有两个方法加了 @Adaptive 注解。
      • Dubbo 会在运行时生成代理类,在代理方法内部根据传入的 URL 参数的 protocol 值决定使用哪个实现类。
      • 如果 URL 参数中没指定协议,就用默认的 dubbo;如果指定了其他值(如 http),则加载对应的实现类。
  • 实现特点

    • 微内核 + 可插拔:保留一个接口和多个实现,运行时可替换。
    • 组件化:如 Protocol 负责 RPC 调用,可以替换为自定义的 RPC 组件。
    • 动态扩展:可通过 URL 参数或配置文件,在运行时动态切换实现类。
    • 增强的 SPI:相比 Java 原生的 SPI 机制,Dubbo 的实现支持:
      • 支持指定默认的实现类(@SPI 注解的默认值)
      • 运行时动态选择实现类(@Adaptive
      • 接口定义(扩展点)自动生成代理类
  • 使用总结

    • Dubbo SPI 本质上是一个运行时可扩展、可替换的组件机制。
    • Dubbo 大量核心组件(如 ProtocolClusterRegistry 等)都是用这种 SPI 机制实现扩展的。
    • 通过 @SPI 注解 + 配置文件来确定默认的实现类,通过 @Adaptive 注解 + URL 参数来实现动态切换实现类。
    • Dubbo 的 SPI 文件路径和 Java SPI 的不一样,核心目录是 resources/META-INF/dubbo/接口全限定名,Dubbo 内部使用的扩展目录是 resources/META-INF/dubbo/internal/接口全限定名,文件内容是 key=实现类的全限定名

加载流程

Dubbo SPI 机制加载扩展的核心步骤:

  • (1) 读取并解析配置文件
  • (2) 缓存所有扩展实现类
  • (3) 基于用户执行的扩展名,实例化对应的扩展实现类
  • (4) 执行扩展实例属性的 IOC 注入(基于 Setter 注入),以及实例化扩展的包装类,实现 AOP 特性

Dubbo SPI 机制加载扩展的整个流程:

参考资料