Dubbo SPI

本贴最后更新于 958 天前,其中的信息可能已经物是人非

Dubbo 在设计之初,为了架构的灵活性,在它架构中的每层为每个功能点都提供了一个 SPI 扩展接口(Dubbo 增强 SPI),Dubbo 框架在使用扩展点功能的时候是对接口进行依赖,而一个扩展接口对应了一系列的扩展实现类,那么如何选择具体的实现类呢?让我们来看看源码吧!

我们以 Protocol 接口为例:

可以看到 SPI 注解中的值为 dubbo,Dubbo 在运行过程中会使用动态编译技术为接口 Protocol 生成一个适配器类 Protocol$Adaptive 的实例,而这个实例的实现就对应 SPI 注解中所配置的 dubbo 对应的类的实现(具体是在 META-INFO/dubbo、META-INFO/dubbo/internal、META-INFO/services 这三个文件夹下的配置中配置的实现类),下面让我们看看 Dubbo 到底是怎么生成这个 Adaptive 的实例的吧!

@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(); default List<ProtocolServer> getServers() { return Collections.emptyList(); } }

在 Provider 方我们会用到一个类叫 ServiceConfig,而在 ServiceConfig 中维护了一个静态内部常量 protocol,它使用了 ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension() 来获取是配置对象。

640.png

我们进入 getExtensionLoader 方法中可以看到这是一个静态方法,并且它为每一个 Dubbo 扩展接口都维护了一个 ExtensionLoader 实例,通过这个方法可以拿到对应 Dubbo 扩展接口的 ExtensionLoader。

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { // 根据扩展接口的class来获取loader EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }

然后通过调用 ExtensionLoader 的实例方法 getAdaptiveExtension() 来获取适配器实例,这里的调用链比较长。

首先 ExtensionLoader 中维护了一个实例的缓存 cachedAdaptiveInstance,先判断缓存中是否存在,如果存在直接返回,然后通过双重校验的方式保证只会实例化一次,通过调用 createAdaptiveExtension() 方法来创建实例。

public T getAdaptiveExtension() { // 缓存 Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { // 获取实例 instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t); } } } } else { throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } } return (T) instance; }

进入到 createAdaptiveExtension() 方法,这里的 injectExtension 方法主要是为 Extension$Adaptive 实例注入依赖,而构建实例的方法是 getAdaptiveExtensionClass().newInstance(),其中 getAdaptiveExtensionClass() 是生成动态编译的类,将该类注入到对应的 ClassLoader 中,而生成实例则是调用 newInstance() 方法。

private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }

接下来进入到 getAdaptiveExtensionClass() 方法中,看看动态编译的类是怎么生成的。
可以看到 Dubbo 会先从 META-INF 路径下读取配置文件并加载 class 文件,在这里面会读取扩展接口上的 @SPI 的值,这里有个不一样的地方,大家知道这个适配器是为了选择自己想要的扩展,但是 Dubbo 将实现这个功能中的 Compiler(动态编译)也弄成了一个扩展,如果按照之前设计的走向,那么就会变成套娃无限循环,就是选择 Compiler 的一个实例需要一个 Compiler 实例去动态编译它,所以 Dubbo 又扩展了一个注解 @Adaptive,也就是这里的第二步,Adaptive 注解的作用用通俗的说法就是适配器的适配器,拿 Compiler 举例,@SPI 的适配器并不会选择最终的 Compiler 去实例化,而是会选择打上了 Adaptive 注解的 Compiler 去实例化,而这个打上了 Adaptive 注解的类会去实例化我们最终选择的 Compiler 类,可以在 dubbo-common 项目的 META-INFO/dubbo.internal 文件夹中找到它,而这个 AdaptiveCompiler 的 compile 方法会再去获取一次 SPI 注解上配置的 Compiler,然后用这个 Compiler 进行动态编译。

// ExtensionLoader.java private Class<?> getAdaptiveExtensionClass() { // 1 从META-INF路径下的文件中读取定义的class文件 // 这里会从几个固定的路径去读取配置文件信息 getExtensionClasses(); // 2 如果存在@Adaptive注解的类,则优先使用这个类 if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } // 3 通过扩展接口上的注解配置名称来创建Adaptive的Class对象 return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
private Class<?> createAdaptiveExtensionClass() { String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); ClassLoader classLoader = findClassLoader(); // 获取Compiler,如果没有Compiler则会先初始化Compiler org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader); }
// ExtensionLoader.java private Map<String, Class<?>> loadExtensionClasses() { // 获取SPI注解上的扩展名 cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap<>(); // 从固定的几个路径中读取配置的class // loadDirectory中会处理Adaptive注解的class,会将其缓存到cachedAdaptiveClass变量中 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); return extensionClasses; }

641.png

// AdaptiveCompiler.java public Class<?> compile(String code, ClassLoader classLoader) { Compiler compiler; ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); String name = DEFAULT_COMPILER; // copy reference if (name != null && name.length() > 0) { compiler = loader.getExtension(name); } else { // 这里获取@SPI上配置的Compiler compiler = loader.getDefaultExtension(); } return compiler.compile(code, classLoader); }

所以可以看到,在第一次获取 SPI 的实例对象的时候,预加载完 class 文件后,如果没有找到对应的 Adaptive 类则会进行 class 的构建,而第一次构建的时候 Compiler 并没有被初始化,这个时候会先走一遍 Compiler 的初始化,将 AdaptiveCompiler 作为 Compiler 的对应实例,然后通过 AdaptiveCompiler 去初始化 Compiler 接口 SPI 注解中的对应实例,最后通过 Compiler 对需要的实例类进行初始化,最后通过 newInstance 获取实例。

ok,源码到这里就分析结束了,谢谢~

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3202 引用 • 8217 回帖 • 1 关注
  • SPI

    Service Provider Interface

    12 引用 • 2 回帖
  • What
    6 引用
  • Dubbo

    Dubbo 是一个分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,是 [阿里巴巴] SOA 服务化治理方案的核心框架,每天为 2,000+ 个服务提供 3,000,000,000+ 次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。

    60 引用 • 82 回帖 • 617 关注

相关帖子

回帖

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 269 关注
  • RabbitMQ

    RabbitMQ 是一个开源的 AMQP 实现,服务器端用 Erlang 语言编写,支持多种语言客户端,如:Python、Ruby、.NET、Java、C、PHP、ActionScript 等。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。

    49 引用 • 60 回帖 • 352 关注
  • 微服务

    微服务架构是一种架构模式,它提倡将单一应用划分成一组小的服务。服务之间互相协调,互相配合,为用户提供最终价值。每个服务运行在独立的进程中。服务于服务之间才用轻量级的通信机制互相沟通。每个服务都围绕着具体业务构建,能够被独立的部署。

    96 引用 • 155 回帖 • 1 关注
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖 • 1 关注
  • Vue.js

    Vue.js(读音 /vju ː/,类似于 view)是一个构建数据驱动的 Web 界面库。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

    268 引用 • 666 回帖 • 1 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖 • 1 关注
  • CAP

    CAP 指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

    12 引用 • 5 回帖 • 633 关注
  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1062 引用 • 3455 回帖 • 151 关注
  • HBase

    HBase 是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的 Google 论文 “Bigtable:一个结构化数据的分布式存储系统”。就像 Bigtable 利用了 Google 文件系统所提供的分布式数据存储一样,HBase 在 Hadoop 之上提供了类似于 Bigtable 的能力。

    17 引用 • 6 回帖 • 71 关注
  • 大疆创新

    深圳市大疆创新科技有限公司(DJI-Innovations,简称 DJI),成立于 2006 年,是全球领先的无人飞行器控制系统及无人机解决方案的研发和生产商,客户遍布全球 100 多个国家。通过持续的创新,大疆致力于为无人机工业、行业用户以及专业航拍应用提供性能最强、体验最佳的革命性智能飞控产品和解决方案。

    2 引用 • 14 回帖 • 1 关注
  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    46 引用 • 114 回帖 • 167 关注
  • CSS

    CSS(Cascading Style Sheet)“层叠样式表”是用于控制网页样式并允许将样式信息与网页内容分离的一种标记性语言。

    198 引用 • 543 回帖 • 3 关注
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    36 引用 • 200 回帖 • 39 关注
  • CSDN

    CSDN (Chinese Software Developer Network) 创立于 1999 年,是中国的 IT 社区和服务平台,为中国的软件开发者和 IT 从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。

    14 引用 • 155 回帖
  • Typecho

    Typecho 是一款博客程序,它在 GPLv2 许可证下发行,基于 PHP 构建,可以运行在各种平台上,支持多种数据库(MySQL、PostgreSQL、SQLite)。

    12 引用 • 67 回帖 • 444 关注
  • 友情链接

    确认过眼神后的灵魂连接,站在链在!

    24 引用 • 373 回帖
  • Bootstrap

    Bootstrap 是 Twitter 推出的一个用于前端开发的开源工具包。它由 Twitter 的设计师 Mark Otto 和 Jacob Thornton 合作开发,是一个 CSS / HTML 框架。

    18 引用 • 33 回帖 • 649 关注
  • ZeroNet

    ZeroNet 是一个基于比特币加密技术和 BT 网络技术的去中心化的、开放开源的网络和交流系统。

    1 引用 • 21 回帖 • 650 关注
  • Log4j

    Log4j 是 Apache 开源的一款使用广泛的 Java 日志组件。

    20 引用 • 18 回帖 • 37 关注
  • WebSocket

    WebSocket 是 HTML5 中定义的一种新协议,它实现了浏览器与服务器之间的全双工通信(full-duplex)。

    48 引用 • 206 回帖 • 280 关注
  • 开源中国

    开源中国是目前中国最大的开源技术社区。传播开源的理念,推广开源项目,为 IT 开发者提供了一个发现、使用、并交流开源技术的平台。目前开源中国社区已收录超过两万款开源软件。

    7 引用 • 86 回帖
  • VirtualBox

    VirtualBox 是一款开源虚拟机软件,最早由德国 Innotek 公司开发,由 Sun Microsystems 公司出品的软件,使用 Qt 编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。

    10 引用 • 2 回帖 • 18 关注
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    591 引用 • 3528 回帖
  • PostgreSQL

    PostgreSQL 是一款功能强大的企业级数据库系统,在 BSD 开源许可证下发布。

    22 引用 • 22 回帖
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    730 引用 • 1283 回帖
  • Scala

    Scala 是一门多范式的编程语言,集成面向对象编程和函数式编程的各种特性。

    13 引用 • 11 回帖 • 155 关注
  • 博客

    记录并分享人生的经历。

    273 引用 • 2389 回帖