Dubbo源码解读-dubbo的SPI机制

上篇我们介绍了Dubbbo整合Spring中的@DubboComponentScan注解源码分析,地址如下

Dubbo源码解读-dubbo启动与Spring整合之@ DubboComponentScan-CSDN博客

        本文主要针对Dubbo的SPI机制,从dubbo源码角度解析。

        Dubbo SPI机制,是Dubbo中比较重要的技术手段,也是面试过程中比较常问的技术问题,大家可以好好仔细读一下本文。有疑问欢迎留言。

        接着说明,读Dubbo源码最好是先对Spring源码有一定的了解。如果大家需要,我也可以针对Spring框架做一系列源码的解读专栏。

         不过不用担心,如果需要Spring的源码知识,文章中也会进行Spring源码铺垫介绍的。

        如果内容中有没描述清楚的,或者大家在阅读源代码有疑问的,欢迎留言,看到就会及时回复。

        为了更清楚的分析解释源码,源代码中部分不重要的内容可能会删减,保留重要内容方便大家理解。

        白天没时间写文章,凌晨两点写作此文,实属不易呀兄弟们!

本文主要内容

  1. SPI机制好处
  2. 自定义SPI接口如何实现
  3. SPI机制加载的文件目录
  4. getExtension(String name):源码解读。
  5. Dubbo中SPI主要方法

SPI机制优势:

主要方便扩展,符合基本开闭原则。

自定义SPI接口如何实现

  1. 定义接口
  2. 接口增加@SPI注解
  3. 实现扩展类
  4. 指定文件夹下配置扩展文件,内容:key:class
#如dubbo.jar中META-INF/dubbo/internal目录下com.alibaba.dubbo.rpc.cluster.Cluster文件内容
mock=com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterWrapper
failover=com.alibaba.dubbo.rpc.cluster.support.FailoverCluster
failfast=com.alibaba.dubbo.rpc.cluster.support.FailfastCluster
failsafe=com.alibaba.dubbo.rpc.cluster.support.FailsafeCluster
failback=com.alibaba.dubbo.rpc.cluster.support.FailbackCluster
forking=com.alibaba.dubbo.rpc.cluster.support.ForkingCluster
available=com.alibaba.dubbo.rpc.cluster.support.AvailableCluster
mergeable=com.alibaba.dubbo.rpc.cluster.support.MergeableCluster
broadcast=com.alibaba.dubbo.rpc.cluster.support.BroadcastCluster

SPI机制加载文件目录

  1. META-INF/services/
  2. META-INF/dubbo/
  3. META-INF/dubbo/internal/

getExtension(String name):源码解读。

功能说明:

        ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo")。此SPI方法是根据配置key名称获取对应的Protocol类。Dubbo其他SPI方法都与此方法比较类似,且基于此方法实现。所以从源码角度,重点讲一下此方法。

具体流程:

  1. 获取ExtensionLoader。每个SPI接口对应一个ExtensionLoader。
  2. cachedInstances:优先根据name从map中获取Holder<Object>
  3. map没有,则进行创建createExtension(name).红色表明的属性,会在其他SPI函数中使用。
    1. getExtensionClasses()。获取扩展类集合Map,缓存建立各种映射关系
      1. loadExtensionClasses:从本地文件中加载key和类的关系
        1. 设置cachedDefaultName:SPI注解的value
        2. 如果类有注解@Adaptive。设置cachedAdaptiveClass:@Adaptive
        3. 如果是包装类:cachedWrapperClasses:设置包装类型集合
        4. 其他:(不包括@Adaptive和包装类)
          1. cachedActivates:建立名称和类上@Activate注解映射Map<String, Activate>
          2. cachedNames:建立类和名称的映射ConcurrentMap<Class<?>, String>
          3. 建立名称和类的映射装成Map<String, Class<?>>
      2. 放入缓存cachedClasses:名称和类的映射装成Holder<Map<String, Class<?>>>
    2. .根据class从缓存EXTENSION_INSTANCES获取实例ConcurrentMap<Class<?>, Object>
    3. 缓存没有,根据class反射创建实例,放入缓存。
    4. IOC属性注入:injectExtension(),也是通过SPI形式,从扩展Factory中拿值。通过SpiExtensionFactory或者SpringExtensionFactory获取依赖对象。
      1. 遍历类方法中所有以set开头的
    5. 判断包装类集合是否为空【有可能返回实例,就不是名称对应的实例,而是被包装的实例】
      1. 不为空,则实例化包装类,且对包装类进行ioc,责任链模式(ProtocolFilterWrapper->ProtocolListenerWrapper->ProtocolListenerWrapper->DubboProtocol)

总结:

其实上面流程已经非常详细了,几乎已经是源代码级别了。但如果只是应付面试,想了解大概流程。可以简单总结如下。

  1. 根据本地文件夹夹在文件,建立key和class映射关系。
  2. 获取对应的class,进行反射实例化。
  3. IOC依赖注入,也是使用SPI实现。
  4. 判断类型对应的配置文件中是否包含包装类,如果存在则对当前类进行wrapper包装。

源码解析:

  • ExtendLoader.getExtendloder()。流程1
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {//如果接口上没有@SPI注解,则报错if (!withExtensionAnnotation(type)) {throw new IllegalArgumentException("Extension type(" + type +") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");}//从缓存中获取ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);if (loader == null) {//每一个@SPI接口类型都会对应一个ExtensionLoader对象EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);}return loader;}
  • ExtensionLoader.getEtension()。流程2
    public T getExtension(String name) {//缓存实例中取Holder<Object> holder = cachedInstances.get(name);if (holder == null) {cachedInstances.putIfAbsent(name, new Holder<Object>());holder = cachedInstances.get(name);}Object instance = holder.get();if (instance == null) {synchronized (holder) {instance = holder.get();if (instance == null) {//创建实例instance = createExtension(name);holder.set(instance);}}}return (T) instance;}
  • ExtensionLoader.createExtension()。流程3
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);}//对类进行iocinjectExtension(instance);//该类是否有包装类Set<Class<?>> wrapperClasses = cachedWrapperClasses;if (wrapperClasses != null && !wrapperClasses.isEmpty()) {for (Class<?> wrapperClass : wrapperClasses) {//实例化包装类,且对包装类进行ioc,责任链模式instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));}}return instance;} catch (Throwable t) {throw new IllegalStateException("Extension instance(name: " + name + ", class: " +type + ")  could not be instantiated: " + t.getMessage(), t);}}
  • ExtensionLoader.loadExtensionClasses() 。流程3.1。设置cachedDefaultName,加载多目录
private Map<String, Class<?>> loadExtensionClasses() {final SPI defaultAnnotation = type.getAnnotation(SPI.class);if (defaultAnnotation != null) {String value = defaultAnnotation.value();//如果@SPI注解中有value值if ((value = value.trim()).length() > 0) {String[] names = NAME_SEPARATOR.split(value);if (names.length > 1) {throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()+ ": " + Arrays.toString(names));}//把value值设置到 cachedDefaultName,,这个就是默认的实现类if (names.length == 1) cachedDefaultName = names[0];}}/*** 从下面的地址中加载这个类型的数据到extensionClasses中* META-INF/dubbo/internal/* META-INF/dubbo/* META-INF/services/*/Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);loadDirectory(extensionClasses, DUBBO_DIRECTORY);loadDirectory(extensionClasses, SERVICES_DIRECTORY);return extensionClasses;}
  •  ExtensionLoader.loadClass() 。流程3.1.1这部是最主要的。设置ExtensionLoader类中各种属性。(cachedAdaptiveClass,cachedWrapperClasses,cachedActivates,cachedNames)
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {//如果类类型和接口类型不一致,报错if (!type.isAssignableFrom(clazz)) {throw new IllegalStateException("Error when load extension class(interface: " +type + ", class line: " + clazz.getName() + "), class "+ clazz.getName() + "is not subtype of interface.");}//如果类上面有@Adaptive注解if (clazz.isAnnotationPresent(Adaptive.class)) {if (cachedAdaptiveClass == null) {//把类赋值给cachedAdaptiveClass,,这个cachedAdaptiveClass后面要返回的cachedAdaptiveClass = clazz;//如果有超过一个实现类上面有@Adaptive注解,报错} else if (!cachedAdaptiveClass.equals(clazz)) {throw new IllegalStateException("More than 1 adaptive class found: "+ cachedAdaptiveClass.getClass().getName()+ ", " + clazz.getClass().getName());}//如果是包装类,包装类必然是持有目标接口的引用的,有目标接口对应的构造函数} else if (isWrapperClass(clazz)) {Set<Class<?>> wrappers = cachedWrapperClasses;if (wrappers == null) {cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();wrappers = cachedWrapperClasses;}//把类添加到包装类集合中wrappers.add(clazz);} else {//获取类的无参构造函数,如果是包装类,这里会报错,,其实这里包装类走不进来了,包装类先处理的clazz.getConstructor();//如果没有配置keyif (name == null || name.length() == 0) {//如果类有Extension注解,则是注解的value,如果没注解则是类名称的小写做为namename = findAnnotationName(clazz);if (name.length() == 0) {throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);}}String[] names = NAME_SEPARATOR.split(name);if (names != null && names.length > 0) {Activate activate = clazz.getAnnotation(Activate.class);if (activate != null) {//如果类上面有@Activate注解,则建立名称和注解的映射cachedActivates.put(names[0], activate);}for (String n : names) {if (!cachedNames.containsKey(clazz)) {//这里建立类和名称的关系cachedNames.put(clazz, n);}Class<?> c = extensionClasses.get(n);if (c == null) {//这个map是要返回的,建立名称和类的关系extensionClasses.put(n, clazz);} else if (c != clazz) {throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());}}}}}

Dubbo中SPI其他常用方法

getActivateExtension(url,value[],group)

        此方法主要获取扩展类中含有@Activate注解的类集合,然后再根据url、value以及group对类集合进行过滤,获取匹配的SPI类

        因为依赖的源码上面已经讲的很清晰了,这里就简单介绍一下流程了。

        具体流程如下:

  1. 延用getExtensionClasses();流程如3.
  2. 遍历缓存cachedActivates,Map<String, Activate>
  3. 优先匹配group.
  4. 根据URL参数匹配@Activate的value属性
  5. 根据values[]匹配

getAdaptiveExtension()。

此方主要获取SPI中,有@Adaptive注解的类。

重点注意一下

  1. 如果存在两个就会报错

  2. 如果不存在,但是SPI对应接口上有注解,则会通过javassist动态生成一个代理类。如

具体流程如下:

  1. 根据@Adaptive注解获取实例
  2. 如果获取不到,判断SPI接口方法是否有主机@Adaptive。动态生成字节码,创建对应的代理类。

总结:上面内容中,每个从业务流程和源码角度进行了详细分析,如果大家有疑问或者对文章排版任何方面有建议都可以留言评论,看到都会及时回复大家。

凌晨3.26写完,太卷了!

知识总结,分享不易,全文手敲,欢迎大家点赞评论收藏。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/736094.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux系统中的任务迁移技术

任务迁移技术是指将任务从一个处理器核心&#xff08;CPU核心&#xff09;移动到另一个核心的过程&#xff0c;以实现负载均衡、优化系统性能或者其他系统目标的技术。任务迁移技术在多核系统中具有重要的作用&#xff0c;可以通过动态调整任务位置来避免负载不均衡和性能瓶颈&…

【工具】Git的24种常用命令

相关链接 传送门&#xff1a;>>>【工具】Git的介绍与安装<< 1.Git配置邮箱和用户 第一次使用Git软件&#xff0c;需要告诉Git软件你的名称和邮箱&#xff0c;否则无法将文件纳入到版本库中进行版本管理。 原因&#xff1a;多人协作时&#xff0c;不同的用户可…

Java 基于 Cloneable 接口实现原型模式(浅拷贝与深拷贝)

前言 在业务开发中&#xff0c;有时需要对业务对象进行一次复制&#xff0c;得到一个一模一样的副本。最直观的做法就是重新 new 一个对象&#xff0c;然后将原型对象的值依次设置到克隆对象中&#xff0c;但是这样写代码过于冗余&#xff0c;也不高效。 设计模式中的原型模式…

各个类linux服务器安装docker教程

由于开始经常使用docker搭建环境了&#xff0c;但是的docker在不同的机器上安装总是会遇到许多问题&#xff0c;因此呢&#xff0c;一下决定总结一些docker环境的安装过程。 Centos7 Docker&Docker-Compose安装教程_centos7 docker-compose 安装_MrNeoJeep的博客-CSDN博客…

恒驰上云规划实施解决方案上线华为云官网

华为云与伙伴共同打造联合解决方案 已成为更多企业的数字化转型利器 1月恒驰上云规划实施解决方案 完成上市宣讲并正式上架华为云官网 恒驰上云规划实施解决方案能力全景图&#xff1a;融合厂商云服务能力&#xff0c;一站式高效云迁移 从深入了解企业的本地IT环境、业务特点…

分享一下 iOS 发布/测试证书 申请过程

1.使用 已开通iOS开发者 的账号登陆 Apple Developer Apple Developer 2.点击下图右上角的 Account&#xff08;账户&#xff09; 点击下图中的 certificates&#xff08;证书&#xff09; 然后会挑战至下图所示页面 3.然后先要注册一个 App id 点击 register 就完成了 4.…

【JAVA】CSS2:样式、选择器、伪类、颜色、字体、边框、列表、背景、盒子、布局、浮动

本文介绍了CSS样式、选择器、伪类、像素、颜色、字体、边框、列表、表格属性、背景、盒子、布局与浮动 1.样式 1.1 行内样式 <h1 style"color: aqua;font-size: large;">123</h1> 1.2 内部样式 <style>h1{color: red;font: 100;}</style>…

Oracle SQL优化(读懂执行计划 一)

目录 SQL执行计划的作用示例演示执行计划概念介绍执行计划实例DISPLAY_CURSOR 类型DISPLAY_AWR 类型 指标详解 SQL执行计划的作用 示例演示 执行计划概念介绍 执行计划实例 DISPLAY_CURSOR 类型 DISPLAY_AWR 类型 指标详解

vim基础命令

目录 前言 一.vim基础命令大全 二.vim熟练的好处 三.入门使用命令 四.使用案例 4.1 gg和G 4.2 i 和 u 和 ESC使用 4.3 y$ 和 p 和 u 使用 五.注意事项 前言 启动vim编辑器后自动进入编辑模式&#xff0c;在此模式中输入命令对应vim一个动作&#xff0c;比如&#xff1a;进入编辑…

linux 配置jdk环境变量

1.确保已上传jdk包到指定目录 2.打开终端&#xff0c;使用文本编辑器&#xff08;比如vi、nano等&#xff09;创建或修改~/.bashrc文件。命令为&#xff1a; sudo vi ~/.bashrc3.在.bashrc文件末添加以下内容&#xff1a; export JAVA_HOME/usr/local/jdk/jdk1.8.0_391 #将…

【Docker】Tensorflow 容器化部署

Tensorflow环境标准软件基于Bitnami Tensorflow 构建。当前版本为2.16.1 你可以通过轻云UC部署工具直接安装部署&#xff0c;也可以手动按如下文档操作&#xff0c;该项目已经全面开源&#xff0c;可以从如下环境获取 配置文件地址: https://gitee.com/qingplus/qingcloud-pla…

理解STM32的低功耗模式

低功耗模式简介 TM32的低功耗模式是特别设计来减少微控制器在不活跃状态下的能耗。这些模式允许STM32在保持核心功能的同时尽可能减少电力消耗&#xff0c;适合用在电池供电或需长期运行的场景。理解各种低功耗模式如何节能&#xff0c;主要包括以下几个方面&#xff1a; 关闭…

C++类和对象(下篇)

目录 一.再谈构造函数 二.static成员 三.友元 四.内部类 五. 再次理解类和对象 一.再谈构造函数 1.构造函数体赋值 在创建对象时&#xff0c;编译器通过调用构造函数&#xff0c;给对象中各个成员变量一个合适的初始值。 class Date { public:Date(int year, int month…

MES系统是怎么进行数据采集的?

在MES管理系统中&#xff0c;数据采集作为最基础也最为关键的一环&#xff0c;对于实现生产过程的透明化、可控好以及优化生产流程具有重要意义。 mes系统是怎么采集数据的? 一、PLC类数据采集&#xff1a;使用C#或C直接编程访问PLC(不需要花钱买组态软件或第三方软件) 二、…

HTML 学习笔记(四)图片

<!--通过图片标签"<img src "图片路径">"来调用图片在网页中进行显示--> <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthd…

C++ 文件操作

目录 C文件操作1. 使用系统函数读写linux平台判断是否存在创建文件 create()接口打开文件 open()接口读取文件 read()接口写文件 write()接口关闭文件 close()接口调整读写文件的位置&#xff08;偏移量&#xff09;lseek()接口获取文件基本信息操作文件状态 fcntl函数 windows…

PostgreSQL索引篇 | GiST索引

GiST索引 PostgreSQL版本为8.4.1 &#xff08;本文为《PostgreSQL数据库内核分析》一书的总结笔记&#xff0c;需要电子版的可私信我&#xff09; GiST&#xff08;Generalized Search Tree&#xff0c;通用搜索树&#xff09;是一种平衡的、树状结构的访问方法。 它在系统中…

java复健01——jdk17、maven3.5、vscode的安装

遇到的问题以及解决方案 Q1. 用choco安装jdk17、jdk20均失败&#xff0c;报一些奇奇怪怪的错 A1&#xff1a; 最稳妥的方法&#xff1a;用管理员权限运行cmd或者shell&#xff0c;用choco search jdk命令看一下有什么版本的jdk&#xff0c;如果列表里没有&#xff0c;应该是…

【数学建模】传染病模型笔记

传染病的基本数学模型&#xff0c;研究传染病的传播速度、空间范围、传播途径、动力学机理等问题&#xff0c;以指导对传染病的有效地预防和控制。常见的传染病模型按照传染病类型分为 SI、SIR、SIRS、SEIR 模型等&#xff0c;按照传播机理又分为基于常微分方程、偏微分方程、网…

代购系统小程序商城APP快速上货采集商品API接口

搜索采集商品使用item_search获取商品id列表&#xff0c;全店商品采集使用item_search_shop获取整店商品id列表&#xff0c;支持翻页显示。获取到的商品id列表再传入获取商品详情接口item_get来获取商品详情页的详情信息。 item_search-按关键字搜索淘宝商品 item_search_sho…