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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: Dubbo的魔法之门:深入解析SPI扩展机制【八】

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


前言

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

Dubbo中的SPI

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

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

  1. 扩展点接口:
  • 扩展点接口是定义扩展点功能的接口,通常由Dubbo提供。例如,com.alibaba.dubbo.rpc.Protocol是Dubbo中的一个扩展点接口,用于定义协议的实现。
  1. 使用@SPI注解:
  • 扩展点接口通常会使用@SPI注解来标识默认的实现类。这个注解用于标记一个接口,并在括号中指定默认的实现类的名字。
  • 例如,在com.alibaba.dubbo.rpc.Protocol接口上可能会有以下注解:
@SPI("dubbo")
public interface Protocol {
    // ...
}
  1. 这表示Protocol接口的默认实现类是dubbo
  2. 配置文件:
  • Dubbo的SPI配置文件仍然位于META-INF/dubbo/目录下,但它的作用不同于Java标准的SPI配置文件。Dubbo的SPI配置文件主要用于指定各个扩展点的实现类的别名,而不是具体的实现类。
  • 例如,Dubbo的SPI配置文件中可能包含以下内容:
dubbo=com.alibaba.dubbo.rpc.protocol.DubboProtocol
hessian=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol
  1. 这些配置将不同的实现类与别名关联起来。
  2. 使用扩展点:
  • 在Dubbo的XML配置文件中,你可以使用扩展点的别名来指定使用哪个实现类。这个别名通常与Dubbo SPI配置文件中的配置相对应。
  • 例如:
<dubbo:protocol name="dubbo" />
  1. 这表示使用了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 方法作为入口,对拓展类对象的获取过程进行详细的分析。

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;
    }

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

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整个执行逻辑:

getExtension(String name)  #根据key获取拓展对象
    -->createExtension(String name) #创建拓展实例
        -->getExtensionClasses #根据路径获取所有的拓展类
            -->loadExtensionClasses #加载拓展类
                -->cacheDefaultExtensionName #解析@SPI注解
            -->loadDirectory #方法加载指定文件夹配置文件
                -->loadResource #加载资源
                    -->loadClass #加载类,并通过 loadClass 方法对类进行缓存
相关文章
|
1月前
【通信协议讲解】单片机基础重点通信协议解析与总结之SPI(二)
【通信协议讲解】单片机基础重点通信协议解析与总结之SPI(二)
|
5月前
|
Java 开发者 Spring
深入解析这两种扩展机制的工作原理和用法
深入解析这两种扩展机制的工作原理和用法
|
2月前
|
缓存 负载均衡 Dubbo
Dubbo技术深度解析及其在Java中的实战应用
Dubbo是一款由阿里巴巴开源的高性能、轻量级的Java分布式服务框架,它致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
72 6
|
2月前
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
180 3
|
2月前
|
设计模式 存储 算法
PHP中的设计模式:策略模式的深入解析与应用在软件开发的浩瀚海洋中,PHP以其独特的魅力和强大的功能吸引了无数开发者。作为一门历史悠久且广泛应用的编程语言,PHP不仅拥有丰富的内置函数和扩展库,还支持面向对象编程(OOP),为开发者提供了灵活而强大的工具集。在PHP的众多特性中,设计模式的应用尤为引人注目,它们如同精雕细琢的宝石,镶嵌在代码的肌理之中,让程序更加优雅、高效且易于维护。今天,我们就来深入探讨PHP中使用频率颇高的一种设计模式——策略模式。
本文旨在深入探讨PHP中的策略模式,从定义到实现,再到应用场景,全面剖析其在PHP编程中的应用价值。策略模式作为一种行为型设计模式,允许在运行时根据不同情况选择不同的算法或行为,极大地提高了代码的灵活性和可维护性。通过实例分析,本文将展示如何在PHP项目中有效利用策略模式来解决实际问题,并提升代码质量。
|
3月前
|
负载均衡 Dubbo Java
Dubbo服务Spi机制和原理
该文章主要介绍了Dubbo中的SPI(Service Provider Interface)机制和原理,包括SPI的基本概念、Dubbo中的SPI分类以及SPI机制的实现细节。
Dubbo服务Spi机制和原理
|
2月前
crash扩展 —— trace解析
crash扩展 —— trace解析
|
3月前
|
存储 关系型数据库 MySQL
深入解析 MySQL 中的扩展
【8月更文挑战第31天】
54 0
|
4月前
|
域名解析 安全 物联网
阿里云EMAS HTTPDNS 扩展全球服务节点:提升解析安全性与网络覆盖
阿里云EMAS HTTPDNS新增国内西南、华南及国际欧洲、美东服务节点,提升了全球覆盖能力与性能。作为高效域名解析服务,EMAS HTTPDNS针对互联网、汽车、物流、IOT等行业提供支持,解决了传统解析易遭劫持等问题。新增节点优化了就近调度功能,显著缩短响应时间并增强了服务稳定性和连续性,尤其为中国企业的海外业务提供了强有力的支持。此次扩展展现了阿里云对服务质量的持续追求和全球市场布局的战略思考。
|
3月前
|
缓存 Dubbo Java
Dubbo线程模型设计解析
该文章主要介绍了Dubbo线程模型的设计解析,包括Dubbo作为一个支持大量并发请求的网络框架的特点,以及其线程模型的工作原理。

推荐镜像

更多