首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Dubbo的魔法之门:深入解析SPI扩展机制【八】

Dubbo的魔法之门:深入解析SPI扩展机制【八】

作者头像
一只牛博
发布2025-05-30 18:58:41
发布2025-05-30 18:58:41
11400
代码可运行
举报
运行总次数:0
代码可运行

欢迎来到我的博客,代码的世界里,每一行都是一个故事

Dubbo的魔法之门:深入解析SPI扩展机制【八】

前言

Dubbo作为一款流行的分布式服务框架,其强大的扩展机制让它成为众多Java应用的首选。但是,这其中的秘密究竟是什么?本文将带你穿越Dubbo的魔法之门,深入研究SPI机制,探索其神奇的工作方式,以及如何在你的Dubbo项目中利用它来实现更灵活的架构。

Dubbo中的SPI

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。

Dubbo的SPI机制包括以下关键点:

扩展点接口:

  • 扩展点接口是定义扩展点功能的接口,通常由Dubbo提供。例如,com.alibaba.dubbo.rpc.Protocol是Dubbo中的一个扩展点接口,用于定义协议的实现。

使用@SPI注解:

  • 扩展点接口通常会使用@SPI注解来标识默认的实现类。这个注解用于标记一个接口,并在括号中指定默认的实现类的名字。
  • 例如,在com.alibaba.dubbo.rpc.Protocol接口上可能会有以下注解:
代码语言:javascript
代码运行次数:0
运行
复制
@SPI("dubbo")
public interface Protocol {
    // ...
}

这表示Protocol接口的默认实现类是dubbo

配置文件:

  • Dubbo的SPI配置文件仍然位于META-INF/dubbo/目录下,但它的作用不同于Java标准的SPI配置文件。Dubbo的SPI配置文件主要用于指定各个扩展点的实现类的别名,而不是具体的实现类。
  • 例如,Dubbo的SPI配置文件中可能包含以下内容:
代码语言:javascript
代码运行次数:0
运行
复制
dubbo=com.alibaba.dubbo.rpc.protocol.DubboProtocol
hessian=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol

这些配置将不同的实现类与别名关联起来。

使用扩展点:

  • 在Dubbo的XML配置文件中,你可以使用扩展点的别名来指定使用哪个实现类。这个别名通常与Dubbo SPI配置文件中的配置相对应。
  • 例如:
代码语言:javascript
代码运行次数:0
运行
复制
<dubbo:protocol name="dubbo" />

这表示使用了dubbo别名对应的com.alibaba.dubbo.rpc.protocol.DubboProtocol实现类。

在Dubbo中,需要使用@SPI注解来标识扩展点接口的默认实现类,并且SPI配置文件的作用是指定别名与具体实现类的映射关系。这个机制允许你在配置中轻松切换不同的实现类,而不需要直接在XML配置中写全限定类名。再次为之前的回答的不准确信息道歉。

dubbo SPI的优点

  1. 能够实现按需加载,JDK SPI仅仅通过接口类名获取所有实现,在通过迭代器获取指定实现,而ExtensionLoader则通过接口类名和key值获取一个实现
  2. 支持AOP(将实现类包装在Wrapper中,Wrapper中实现公共增强逻辑)
  3. 支持IOC(能够通过set方法注入其他扩展点)

源码分析

下面我们从 ExtensionLoader 的 getExtension 方法作为入口,对拓展类对象的获取过程进行详细的分析。

代码语言:javascript
代码运行次数:0
运行
复制
    public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
             // 获取默认的拓展实现类
            return getDefaultExtension();
        }
        // Holder,顾名思义,用于持有目标对象
        Holder<Object> holder = getOrCreateHolder(name);
        Object instance = holder.get();
         // 双重检查
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    // 创建拓展实例
                    instance = createExtension(name);
                    // 设置实例到 holder 中
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }

上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。下面我们来看一下创建拓展对象的过程是怎样的。

代码语言:javascript
代码运行次数:0
运行
复制
private T createExtension(String name) {
    // 从配置文件中加载所有的拓展类,可得到“配置项名称”到“配置类”的映射关系表
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            // 通过反射创建实例
            EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        // 向实例中注入依赖
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (CollectionUtils.isNotEmpty(wrapperClasses)) {
              // 循环创建 Wrapper 实例
            for (Class<?> wrapperClass : wrapperClasses) {
                // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
                // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
                instance = injectExtension(
                    (T) wrapperClass.getConstructor(type).newInstance(instance));
            }
        }
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("...");
    }
}

createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:

  1. 通过 getExtensionClasses 获取所有的拓展类
  2. 通过反射创建拓展对象
  3. 向拓展对象中注入依赖
  4. 将拓展对象包裹在相应的 Wrapper 对象中

以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。由于此类设计源码较多,这里简单的总结下ExtensionLoader整个执行逻辑:

代码语言:javascript
代码运行次数:0
运行
复制
getExtension(String name)  #根据key获取拓展对象
    -->createExtension(String name) #创建拓展实例
        -->getExtensionClasses #根据路径获取所有的拓展类
            -->loadExtensionClasses #加载拓展类
                -->cacheDefaultExtensionName #解析@SPI注解
            -->loadDirectory #方法加载指定文件夹配置文件
                -->loadResource #加载资源
                    -->loadClass #加载类,并通过 loadClass 方法对类进行缓存
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Dubbo的魔法之门:深入解析SPI扩展机制【八】
  • 前言
  • Dubbo中的SPI
  • dubbo SPI的优点
  • 源码分析
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档