Dubbo源码解读-dubbo的SPI机制

news/2024/7/5 4:06:08

上篇我们介绍了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);
            }
            //对类进行ioc
            injectExtension(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();
            //如果没有配置key
            if (name == null || name.length() == 0) {
                //如果类有Extension注解,则是注解的value,如果没注解则是类名称的小写做为name
                name = 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://lihuaxi.xjx100.cn/news/2084488.html

相关文章

详细讲解Xilinx DDR3 的MIG IP生成步骤及参数含义

前几篇文章讲解了SDRAM到DDR3各自的变化&#xff0c;本文讲解如何使用DDR3&#xff0c;在Altera的Cyclone IV开发板上一般会使用SDRAM作为存储数据的芯片&#xff0c;而Xilinx的S6和7000系列一般使用DDR3作为存储数据的芯片。 从SDRAM芯片内部结构分析其原理&#xff0c;从内部…

数据结构小记【Python/C++版】——散列表篇

一&#xff0c;基础概念 散列表&#xff0c;英文名是hash table&#xff0c;又叫哈希表。 散列表通常使用顺序表来存储集合元素&#xff0c;集合元素以一种很分散的分布方式存储在顺序表中。 散列表是一个键值对(key-item)的组合&#xff0c;由键(key)和元素值(item)组成。键…

算法-双指针、BFS与图论-1224. 交换瓶子

题目 思路 可以交换任意两个瓶子&#xff0c;最多n-1次&#xff1b;如果是只能交换相邻的瓶子&#xff0c;那么相当于逆序对的个数&#xff08;这篇博客是介绍如何计算逆序对的算法&#xff1a;算法篇&#xff1a;逆序对_逆序对算法-CSDN博客&#xff09;本题转换为图论去看:边…

FFmpeg——开源的开源的跨平台音视频处理框架简介

引言&#xff1a; FFmpeg是一个开源的跨平台音视频处理框架&#xff0c;可以处理多种音视频格式。它由Fabrice Bellard于2000年创建&#xff0c;最初是一个只包括解码器的项目。后来&#xff0c;很多开发者参与其中&#xff0c;为FFmpeg增加了多种新的功能&#xff0c;例如编码…

linux安全配置规范

一、 概述 1.1 适用范围 本配置规范适用于凝思操作系统&#xff0c;主要涉及LINUX操作系统安全配置方面的基本要求&#xff0c;用于指导LINUX操作系统安全加固工作&#xff0c;落实信息安全等级保护等保三级系统操作系统安全配置&#xff0c;为主机安全配置核查提供依据。…

记一次因为共享缓存导致流水号重复的问题排查过程

背景&#xff1a; 在开发日终应用或者跑批应用的时候&#xff0c;进行每天凌晨跑批或者全天跑批多次进行表数据清理的时候&#xff0c;每次清理都会登记操作明细到日志表&#xff0c;流水号是根据Oracle号段模式获取1000个流水号段放内存里&#xff0c;不够用再从数据库重新获取…

03hive数仓安装与基础使用

hive Hive概述 Hive是基于Hadoop的一个数据仓库工具。可以将结构化的数据文件映射为一张表&#xff0c;并提供完整的sql查询功能&#xff0c;本质上还是一个文件底层是将sql语句转换为MapReduce任务进行运行本质上是一种大数据离线分析工具学习成本相当低&#xff0c;不用开发…

QT UI设计

在QT中添加VTK 在main函数中初始化 //VTK的初始化语句 #ifndef INITIAL_OPENGL #define INITIAL_OPENGL #include <vtkAutoInit.h> VTK_MODULE_INIT(vtkRenderingOpenGL); VTK_MODULE_INIT(vtkInteractionStyle); VTK_MODULE_INIT(vtkRenderingVolumeOpenGL); VTK_MODU…