首页 > 编程语言 >Dubbo源码解读-dubbo的SPI机制

Dubbo源码解读-dubbo的SPI机制

时间:2024-03-16 13:59:32浏览次数:29  
标签:Dubbo name dubbo clazz SPI 源码 class

上篇我们介绍了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写完,太卷了!

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

标签:Dubbo,name,dubbo,clazz,SPI,源码,class
From: https://blog.csdn.net/u014336799/article/details/136612692

相关文章

  • 【NVIDIA JETSON AGX XAVIER】与个人笔记本(win11)建立TCP-IP连接相互传输数据(含源码)
    文章目录前言一、个人笔记本(win11)传输数据到XAVIER(多次传输)1.服务器端代码(个人笔记本win11)2.客户端代码(NVIDIAJETSONAGXXAVIER)二、两端相互传输(以另一种形式解决上一篇博客的问题)1.服务器端代码(个人笔记本win11)2.客户端代码(NVIDIAJETSONAGXXAVIER)三、传输数据中......
  • GDCM:实现读取DICOM属性并打印(附完整源码)
    GDCM:实现读取DICOM属性并打印下面是一个使用GDCM库读取DICOM文件属性并打印它们的示例代码:#include<iostream>#include"gdcmReader.h"#include"gdcmFile.h"#include"gdcmDataSet.h"#include"gdcmAttribute.h"intmain(intargc,char*argv[])......
  • java毕设安卓基于安卓的图库管理系统(开题+源码)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着移动互联网技术的快速发展和智能手机普及率的不断攀升,人们对于移动设备上信息管理和共享的需求日益增长。安卓作为目前全球最受欢迎的移动操作系......
  • java毕设安卓基于安卓的汽车租赁系统的设计与实现(开题+源码)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着科技的飞速发展,移动互联网已经渗透到我们生活的方方面面,其中,移动应用在汽车租赁行业中扮演着日益重要的角色。近年来,汽车租赁市场呈现出蓬勃的发......
  • Spring 5.x 源码之旅-59AOP事务的初始化流程一
    作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬学习必须往深处挖,挖的越深,基础越扎实!阶段1、深入多线程阶段2、深入多线程设计模式阶段3、深入juc源码解析阶段4、深入jdk其余源码解析......
  • Spring 5.x 源码之旅-59AOP事务的初始化流程二
    作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬学习必须往深处挖,挖的越深,基础越扎实!阶段1、深入多线程阶段2、深入多线程设计模式阶段3、深入juc源码解析阶段4、深入jdk其余源码解析......
  • 直播软件源码,异常偶有发生我们该如何处理?
    直播软件源码,异常偶有发生我们该如何处理?初识异常异常与异常处理:异常既错误异常会导致程序崩溃并停止运行异常处理可以捕获到异常,将异常部位的程序进行处理使得直播软件源码继续正常运行异常处理的结构由try-except代码块组成try:代码块#被try关键字......
  • 直播带货源码,异步处理中会处理两次请求
    直播带货源码,异步处理中会处理两次请求从序列图上可以看到SpringMVC在处理异步请求时,DispatcherServlet会处理两次请求具体来看HandlerAdapter的处理过程//根据HandlerMethod解析参数并完成过程调用得到一个ModelAndViewprivateModelAndViewinvokeHandleMethod(Ht......
  • 视频直播系统源码,异步处理实现代码分析
    视频直播系统源码,异步处理实现代码分析@OverrideprotectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{System.out.println("doget");method3(request,response);}/***使用asyncConte......
  • springboot仓库管理系统(附:源码+课件)
    项目介绍:管理员system123456客户表(ID客户名称邮编客户地址客户电话联系人联系人电话开户行账号邮箱)供应商表(ID供应商名称邮编供应商地址供应商电话联系人联系人电话开户行账号邮箱)商品表(ID商品名称供应商产地商品规格商品包装生产批号批准文号商......