首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Dubbo 的 SPI 和 JDK 的 SPI 有什么区别?

Dubbo 的 SPI 和 JDK 的 SPI 有什么区别?

原创
作者头像
程序员皮皮林
发布2026-06-06 18:41:09
发布2026-06-06 18:41:09
410
举报

面试考察点

  1. SPI 概念理解:面试官首先想确认你是否知道 SPI 是什么、解决什么问题。如果你连 "Service Provider Interface" 的全称都说不出来,那基本就凉了。
  2. JDK SPI 的局限性:面试官想看你是否踩过 JDK SPI 的坑——全量加载导致资源浪费、没有 IOC 和 AOP 支持、配置方式原始。如果你能说出 "JDK SPI 会一次性加载所有实现类,即使你只需要其中一个",面试官会眼前一亮。
  3. Dubbo SPI 的增强设计:这道题的终极考察点——你是否理解 Dubbo 为什么不用 JDK 原生 SPI,而是自己造了一套。能说出按需加载、依赖注入、自适应扩展、自动激活这几个增强点,说明你是真的读过源码。

核心答案

先说结论:Dubbo SPI 是对 JDK SPI 的增强版,核心区别可以一张表说清楚:

特性

JDK SPI

Dubbo SPI

配置文件路径

META-INF/services/

META-INF/dubbo/

配置格式

全限定类名,每行一个

key=value 格式,如 dubbo=org.apache.dubbo...

加载方式

全量加载所有实现类

按需加载,通过 key 获取指定实现

依赖注入(IOC)

不支持

支持,自动注入依赖组件

AOP 增强

不支持

支持 Wrapper 包装机制

自适应扩展

不支持

@Adaptive 注解,运行时动态选择实现

自动激活

不支持

@Activate 注解,条件自动加载

获取扩展点

ServiceLoader.load()

ExtensionLoader.getExtensionLoader()

一句话概括:**JDK SPI 是 "一把梭全加载",Dubbo SPI 是 "要啥加载啥,还能自动装配"**。

深度解析

一、JDK SPI 的工作方式

先回顾一下 JDK SPI 怎么用的,后面对比才更清晰。

SPI 的全称是 Service Provider Interface,是 JDK 提供的一种服务发现机制。本质就是:接口定义在 A 模块,实现在 B 模块,运行时通过配置文件找到实现类

代码语言:javascript
复制
// 1. 定义接口
public interface Serialization {
    byte[] serialize(Object obj);
    Object deserialize(byte[] data);
}

// 2. 提供实现类
public class JsonSerialization implements Serialization {
    @Override
    public byte[] serialize(Object obj) {
        // JSON 序列化实现
    }
    @Override
    public Object deserialize(byte[] data) {
        // JSON 反序列化实现
    }
}

public class HessianSerialization implements Serialization {
    @Override
    public byte[] serialize(Object obj) {
        // Hessian 序列化实现
    }
    @Override
    public Object deserialize(byte[] data) {
        // Hessian 反序列化实现
    }
}

// 3. 在 META-INF/services/ 下创建配置文件
// 文件名:com.example.Serialization
// 文件内容(全限定类名,每行一个):
// com.example.JsonSerialization
// com.example.HessianSerialization

// 4. 使用 ServiceLoader 加载
ServiceLoader<Serialization> loader = ServiceLoader.load(Serialization.class);
for (Serialization ser : loader) {
    // 问题是:所有实现类都会被实例化,即使你只想用 JsonSerialization
    System.out.println(ser.getClass().getName());
}

JDK SPI 的问题很明显:

  • 全量加载:遍历 ServiceLoader 的时候,所有实现类都会被实例化。假设你有 20 个序列化实现,只想用其中一个,不好意思,20 个全给你 new 出来了。如果某个实现类初始化很重(比如连接数据库),这就是纯纯的资源浪费。
  • 没有 IOC:实现类里如果依赖了其他组件,JDK SPI 不会帮你注入,你得自己处理依赖关系。
  • 没有 AOP:你没法在不修改实现类的前提下给它加功能(比如加个日志、加个监控),不支持装饰器模式。

二、Dubbo SPI 的按需加载

Dubbo SPI 把配置文件改成了 key=value 格式,支持按 key 精确加载:

代码语言:javascript
复制
// 配置文件路径:META-INF/dubbo/com.example.Serialization
// 内容格式(key=value):
// json=com.example.JsonSerialization
// hessian=com.example.HessianSerialization
// fastjson=com.example.FastJsonSerialization

// 使用 Dubbo SPI 按需加载
Serialization jsonSer = ExtensionLoader
    .getExtensionLoader(Serialization.class)
    .getExtension("json");  // 只加载 json 对应的实现类

看到了吧?传一个 "json" 进去,只实例化 JsonSerialization,其他两个实现类根本不会被加载。这就是按需加载。

Dubbo SPI 的核心 API 是 ExtensionLoader,常用的方法有这几个:

方法

作用

示例

getExtension("key")

按 key 获取指定扩展实现

getExtension("json")

getDefaultExtension()

获取 @SPI 注解指定的默认实现

获取 @SPI("hessian") 的默认值

getAdaptiveExtension()

获取自适应扩展实现

运行时根据 URL 参数动态选择

getActivateExtension()

获取满足条件的自动激活扩展

根据 group、value 条件筛选

getSupportedExtensions()

获取所有已注册的扩展 key

返回 [json, hessian, fastjson]

三、Dubbo SPI 的依赖注入

Dubbo SPI 支持在扩展实现类中注入其他扩展点,类似 Spring 的 @Autowired

代码语言:javascript
复制
// Dubbo 自带的 Protocol 扩展示例
public class DubboProtocol implements Protocol {

    // ExtensionLoader 会自动注入这个依赖
    private ExchangeHandler handler;

    // 通过 setter 方法注入,不是通过字段注入
    public void setHandler(ExchangeHandler handler) {
        this.handler = handler;
    }
}

ExtensionLoader 在实例化扩展类之后,会扫描它的 setter 方法,如果参数类型是另一个扩展点接口,就自动把对应的扩展实现注入进来。这个设计思路和 Spring 的依赖注入一模一样,只不过 Dubbo 自己实现了一套轻量版本。

四、自适应扩展 @Adaptive

这个是 Dubbo SPI 最强大的特性之一,也是面试官最爱追问的。

啥叫 "自适应"?就是 在运行时根据参数动态决定用哪个实现类,而不是在编译期写死。

代码语言:javascript
复制
// SPI 接口上标注 @Adaptive
@SPI("dubbo")
public interface Protocol {

    @Adaptive
    void export(URL url);

    @Adaptive
    <T> T refer(Class<T> type, URL url);
}

// 运行时 Dubbo 会根据 URL 中的 protocol 参数动态选择实现
// 如果 URL 是 dubbo://192.168.1.1:20880/...,就用 DubboProtocol
// 如果 URL 是 rest://192.168.1.1:8080/...,就用 RestProtocol

Dubbo 会为带有 @Adaptive 注解的接口方法自动生成一个代理类(或者手动指定代理类)。这个代理类会读取 URL 中的参数值,然后动态选择对应的扩展实现。

img
img

img

上图展示了 @Adaptive 的工作原理:

  • 调用方发起调用时,不会直接和某个具体的 Protocol 实现类打交道,而是调用 Adaptive 代理类。
  • 代理类URL 参数中提取协议类型(比如 dubborestgrpc),然后通过 ExtensionLoader.getExtension() 按需获取对应的实现类。
  • 最终执行的是真正实现类的逻辑,整个过程对调用方完全透明。

这个设计的好处是什么?你写一个通用逻辑,不用 if-else 去判断 "如果协议是 dubbo 就用 DubboProtocol,如果是 rest 就用 RestProtocol"——全都由自适应扩展在运行时自动搞定。说实话,这个设计确实优雅。

五、自动激活 @Activate

@Activate 注解用于标记那些需要 "条件触发" 的扩展,最典型的场景就是 Filter 链

代码语言:javascript
复制
// Dubbo 内置的消费者端日志 Filter
@Activate(group = CommonConstants.CONSUMER)
public class ConsumerTraceFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) {
        // 自动记录调用链路日志
        return invoker.invoke(invocation);
    }
}

// 当你是 Consumer 端时,这个 Filter 会自动被加载到 Filter 链中
// 不需要你手动配置,@Activate(group = "consumer") 帮你搞定了

@Activate 支持多种条件匹配:

属性

作用

group

匹配 Consumer 或 Provider 端

value

匹配 URL 中的 key

order

控制多个激活扩展的执行顺序

before

在指定扩展之前执行

after

在指定扩展之后执行

六、Wrapper 机制(AOP 增强)

Dubbo SPI 还支持 Wrapper 包装,可以在不修改原始实现类的前提下给它加功能:

代码语言:javascript
复制
// 原始 Protocol 实现
public class DubboProtocol implements Protocol {
    public void export(URL url) {
        // 真正的服务暴露逻辑
    }
}

// Wrapper 包装类(实现相同接口 + 构造函数接收原始实例)
public class ProtocolListenerWrapper implements Protocol {
    private Protocol protocol; // 持有原始实例

    public ProtocolListenerWrapper(Protocol protocol) {
        this.protocol = protocol;
    }

    public void export(URL url) {
        // 前置增强:记录日志、添加监听器等
        protocol.export(url);
        // 后置增强:通知监听器
    }
}

ExtensionLoader 发现构造函数只有一个参数且为接口类型时,会自动把它当成 Wrapper,在原始实例外面套一层。可以套多层,形成装饰器链。

这就是 Dubbo SPI 的 AOP 能力,和 Spring 的 AOP 思路一样,但实现更轻量。

面试高频追问

  1. Dubbo SPI 为什么要自己造一套,不用 JDK 的? 就是因为 JDK SPI 全量加载太浪费资源,而且不支持 IOC 和 AOP。Dubbo 作为一个高性能 RPC 框架,对扩展点的加载效率和控制粒度要求很高,JDK SPI 满足不了,所以自己搞了一套。
  2. @Adaptive 注解加在类上和加在方法上有什么区别? 加在方法上:Dubbo 会自动生成一个代理类(代码是拼接字符串生成的 .java 文件然后编译),在代理类里根据 URL 参数动态选择实现。加在类上:直接用这个类作为自适应实现,不再生成代理类。Dubbo 里只有 AdaptiveCompilerAdaptiveExtensionFactory 是加在类上的,其余都加在方法上。
  3. Dubbo SPI 的配置文件除了 META-INF/dubbo/,还会扫描哪些目录? Dubbo 3.x 会依次扫描三个目录:META-INF/dubbo/META-INF/services/META-INF/dubbo/internal/。其中 internal 目录放的是 Dubbo 内置的扩展实现,services 目录是为了兼容 JDK SPI 的配置格式。
  4. Dubbo 里有哪些核心扩展点是通过 SPI 实现的? Protocol(协议)、Serialization(序列化)、Transport(网络传输)、Registry(注册中心)、LoadBalance(负载均衡)、Cluster(集群容错)、Filter(过滤器)、Monitor(监控)——基本你能想到的组件全是通过 SPI 扩展的。

常见面试变体

  • "Dubbo 为什么不用 JDK 原生的 SPI?"
  • "Dubbo SPI 的 @Adaptive 注解是做什么的?"
  • "Dubbo 的扩展点机制是怎么实现的?"
  • "说一下 Dubbo SPI 的加载流程"

记忆口诀

Dubbo SPI 六大增强:按需加载省资源,依赖注入解耦合,自适应选实现,自动激活上 Filter,Wrapper 做 AOP,key=value 好配置。

总结

Dubbo SPI 相比 JDK SPI,核心优势就三件事:按需加载避免资源浪费、IOC + AOP 让扩展点具备完整的依赖注入和装饰器能力、**@Adaptive 自适应扩展**让运行时动态选择实现变得优雅。把这几条说清楚,面试官就知道你不是只停留在 "用过 Dubbo" 的层面,而是真的理解它的设计精髓。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 面试考察点
  • 核心答案
  • 深度解析
    • 一、JDK SPI 的工作方式
    • 三、Dubbo SPI 的依赖注入
    • 四、自适应扩展 @Adaptive
    • 五、自动激活 @Activate
    • 六、Wrapper 机制(AOP 增强)
  • 面试高频追问
  • 常见面试变体
  • 记忆口诀
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档