Dubbo源码篇07---SPI神秘的面纱---原理篇---下

news2024/12/28 5:48:05

Dubbo源码篇07---SPI神秘的面纱---原理篇---下

  • 引言
  • 根据name获取扩展实例对象
  • 获取默认扩展实例对象
  • 按条件批量获取扩展实例对象
    • 实例演示


引言

上篇文章: Dubbo源码篇06—SPI神秘的面纱—原理篇—上 我们追踪了getAdaptiveExtension获取自适应扩展点的整个流程,整个流程核心如下:

private T createAdaptiveExtension() {
        T instance = (T) getAdaptiveExtensionClass().newInstance();
        instance = postProcessBeforeInitialization(instance, null);
        injectExtension(instance);
        instance = postProcessAfterInitialization(instance, null);
        initExtension(instance);
        return instance;
}

private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

因为自适应扩展点在dubbo中的用意是用来实现运行时动态选择实现类的,所以不会对自适应扩展点赋予AOP能力,从上面的流程中我们也没有发现哪里存在Wrapper机制的处理。

所以本文我们顺着普通扩展类加载流程,来过一遍dubbo对AOP的处理过程:

        ApplicationModel applicationModel = ApplicationModel.defaultModel();
        ExtensionLoader<FrameWork> extensionLoader = applicationModel.getExtensionLoader(FrameWork.class);
        FrameWork frameWork = extensionLoader.getExtension("guice");

根据name获取扩展实例对象

根据传入的name作为serviceKey去加载对应的扩展实现:

    public T getExtension(String name) {
        //第二个参数表明是否对当前扩展类启动Wrapper装饰
        T extension = getExtension(name, true);
        if (extension == null) {
            throw new IllegalArgumentException("Not find extension: " + name);
        }
        return extension;
    }
public T getExtension(String name, boolean wrap) {
        ...
        //如果name为true,那么去获取默认扩展实现
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        String cacheKey = name;
        if (!wrap) {
            cacheKey += "_origin";
        }
        //查询缓存--没有新建一个Holder返回
        final Holder<Object> holder = getOrCreateHolder(cacheKey);
        Object instance = holder.get();
        //缓存有,直接返回,否则进入创建逻辑
        if (instance == null) {
            synchronized (holder) {
                instance = holder.get();
                if (instance == null) {
                    //创建扩展类的核心方法
                    instance = createExtension(name, wrap);
                    holder.set(instance);
                }
            }
        }
        return (T) instance;
    }
private T createExtension(String name, boolean wrap) {
        //getExtensionClasses方法上篇文章解析过了,这里跳过
        Class<?> clazz = getExtensionClasses().get(name);
        //如果没有key=name的扩展实现,则抛出异常
        if (clazz == null || unacceptableExceptions.contains(name)) {
            throw findException(name);
        }
        try {
           // 判断对应的扩展实现类型是否已经创建过了实例对象--确保单例性 
            T instance = (T) extensionInstances.get(clazz);
            //如果只是解析了SPI文件,构成了<name,class>缓存,下一步就是为当前扩展类型构建<class,signleInstance>缓存
            if (instance == null) {
                //利用instantiationStrategy实例化扩展实例对象---具体逻辑在InstantiationStrategy中
                //实例化逻辑比较简单: 要不就是默认构造,要么构造函数可以有参数,但是参数类型必须是ScopeModel子类
                extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));
                instance = (T) extensionInstances.get(clazz);
                //前后置处理--依赖注入
                instance = postProcessBeforeInitialization(instance, name);
                injectExtension(instance);
                instance = postProcessAfterInitialization(instance, name);
            }
            //和自适应扩展点创建的不同逻辑: 判断是否需要对当前扩展实例进行装饰
            if (wrap) {
                List<Class<?>> wrapperClassesList = new ArrayList<>();
                //当前扩展类相关wrapper类型搜集工作在getExtensionClasses中完成
                if (cachedWrapperClasses != null) {
                    wrapperClassesList.addAll(cachedWrapperClasses);
                    wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                    Collections.reverse(wrapperClassesList);
                }
                
                //wrapper class搜集是满足存在一个单参数的拷贝构造函数,并且参数类型为当前扩展类类型
                if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                    //不断循环,套娃创建一层层的装饰器对象
                    for (Class<?> wrapperClass : wrapperClassesList) {
                        //Wrapper注解用于实现按条件装饰
                        Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                        //如果wrapper class类上不存在Wrapper注解,那么表示装饰不需要满足任何条件
                        //否则,需要判断条件是否满足,满足才会进行装饰
                        boolean match = (wrapper == null) ||
                            ((ArrayUtils.isEmpty(wrapper.matches()) || ArrayUtils.contains(wrapper.matches(), name)) &&
                                !ArrayUtils.contains(wrapper.mismatches(), name));
                        if (match) {
                            //满足则进入装饰流程
                            //1.实例化当前装饰类,采用的是单参的拷贝构造函数
                            //2.执行依赖注入流程
                            instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
                            //3.执行后置处理流程
                            instance = postProcessAfterInitialization(instance, name);
                        }
                    }
                }
            }

            // Warning: After an instance of Lifecycle is wrapped by cachedWrapperClasses, it may not still be Lifecycle instance, this application may not invoke the lifecycle.initialize hook.
            //调用初始化接口---注意上面警告信息,也就是说经过包装后,我们的包装对象未必继承lifecycle接口,因此初始化调用也就不会发生了
            initExtension(instance);
            return instance;
        } catch (Throwable t) {
            throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                type + ") couldn't be instantiated: " + t.getMessage(), t);
        }
    }

获取默认扩展实例对象

采用@SPI注解中的val值,作为serviceKey去加载对应的扩展实现:

    public T getDefaultExtension() {
        //加载SPI文件,构建相关缓存,如: <name,class>
        getExtensionClasses();
        //cachedDefaultName来自@SPI注解中的val值
        if (StringUtils.isBlank(cachedDefaultName) || "true".equals(cachedDefaultName)) {
            return null;
        }
        //这个流程上面讲过了
        return getExtension(cachedDefaultName);
    }

按条件批量获取扩展实例对象

到现在为止,我们还差extensionLoader.getActivateExtensions()流程没有讲解,下面我们来看看按条件批量获取扩展实例对象是怎样实现的:

    public List<T> getActivateExtension(URL url, String key, String group) {
        //根据传入的key从url中提取出value值
        String value = url.getParameter(key);
        //如果value不为空,则按照","分割,作为serviceKey
        return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
    }
    public List<T> getActivateExtension(URL url, String[] values, String group) {
        checkDestroyed();
        // solve the bug of using @SPI's wrapper method to report a null pointer exception.
        Map<Class<?>, T> activateExtensionsMap = new TreeMap<>(activateComparator);
        List<String> names = values == null ? new ArrayList<>(0) : asList(values);
        Set<String> namesSet = new HashSet<>(names);
        if (!namesSet.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            //集合条件缓存构建: <serviceKey,groups>  和 <serviceKey,keyParis>
            if (cachedActivateGroups.size() == 0) {
                synchronized (cachedActivateGroups) {
                    // cache all extensions
                    if (cachedActivateGroups.size() == 0) {
                        //加载当前扩展类对应的SPI资源文件,并建立好相关缓存映射,此处主要为cachedActivates映射
                        //cachedActivates缓存了<serviceKey,@Activate注解> (serviceKey就是我们在SPI文件: serviceKey=serivceImpl全类名)
                        //如果配置文件中没有指定serviceKey,那么为@Extension注解中指定的val值,如果没有注解,那么就为实现类的简单类名
                        getExtensionClasses();
                        //依次处理当前扩展类下所有标注了@Activate注解的实现类
                        for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                            //key为serviceKey
                            String name = entry.getKey();
                            //activate注解
                            Object activate = entry.getValue();

                            String[] activateGroup, activateValue;
                            //提取注解中的值
                            if (activate instanceof Activate) {
                                activateGroup = ((Activate) activate).group();
                                activateValue = ((Activate) activate).value();
                            } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
                                activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
                                activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
                            } else {
                                continue;
                            }
                            //缓存<serviceKey,groups>映射,即激活当前实现类,需要满足哪些分组要求(满足其一即可)
                            cachedActivateGroups.put(name, new HashSet<>(Arrays.asList(activateGroup)));
                            //缓存<serviceKey,keyParis>映射
                            //activate注解中的value属性有两种写法: key1:val1 或者 key2
                            //前者表示URL中存在key1=val1的键值对才算满足条件
                            //后置表示URL中存在key2即满足条件
                            String[][] keyPairs = new String[activateValue.length][];
                            for (int i = 0; i < activateValue.length; i++) {
                                if (activateValue[i].contains(":")) {
                                    keyPairs[i] = new String[2];
                                    String[] arr = activateValue[i].split(":");
                                    keyPairs[i][0] = arr[0];
                                    keyPairs[i][1] = arr[1];
                                } else {
                                    keyPairs[i] = new String[1];
                                    keyPairs[i][0] = activateValue[i];
                                }
                            }
                            cachedActivateValues.put(name, keyPairs);
                        }
                    }
                }
            }

            // traverse all cached extensions
            //遍历<serviceKey,groups>映射
            cachedActivateGroups.forEach((name, activateGroup) -> {
                // 如果函数调用中传入的group匹配条件为空,或者group存在于groups集合,则满足分组匹配这个条件
                if (isMatchGroup(group, activateGroup)
                 //nameSet是从传入函数中的key,从url中获取value后,按照","分割,得到的集合
                 //这里去掉serviceKey=name的处理,因为该逻辑会在下面被处理   
                    && !namesSet.contains(name)
                    && !namesSet.contains(REMOVE_VALUE_PREFIX + name)
                    //从<serviceKey,keyParis>集合中获取激活当前扩展实现类,需要满足哪些用户自定义条件
                    //这里判断逻辑就是: 如果用户指定的是形如@Active(value="key1:value1, key2:value2")
                    //那么会从url先中取出key1的值,与value1进行比较,相等直接返回true,否则取出key2值继续判断,也就是说这里是任意条件满足就返回true
                    //如果用户注解中只指定了@Active(value="key1"),那么只要url中存在key1,就满足条件
                    && isActive(cachedActivateValues.get(name), url)) {
                    
                    //如果分组条件和用户自定义条件都满足,则加入activateExtensionsMap集合<class,Instance>
                    activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
                }
            });
        }

        if (namesSet.contains(DEFAULT_KEY)) {
           ...
        } else {
            // add extensions, will be sorted by its order
            for (int i = 0; i < names.size(); i++) {
                String name = names.get(i);
                if (!name.startsWith(REMOVE_VALUE_PREFIX)
                    && !namesSet.contains(REMOVE_VALUE_PREFIX + name)) {
                    if (!DEFAULT_KEY.equals(name)) {
                        //<serviceKey,class>集合中包含serviceKey=name
                        if (containsExtension(name)) {
                            //则将serviceKey=name的扩展实现类也加入结果集合
                            activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
                        }
                    }
                }
            }
            //返回最终得到的集合扩展实现类集合
            return new ArrayList<>(activateExtensionsMap.values());
        }
    }

上面这一大段看下来可能会比较懵逼,但是没关系,下图详细解释了按照激活条件筛选的整个流程:
在这里插入图片描述

  • 如果某个serviceKey对应的keyParis为空,也就是说用户没有自定义匹配条件,那么该条件分支默认返回true。
  • 如果函数传入的group为空,那么不考虑分组匹配条件,该条件分支默认返回true

注意: 放入结果前时,扩展类的获取时调用的getExtension方法,意味着按条件批量获取扩展实例对象场景下,实现类是享有AOP(Wrapper机制)支持的:

   activateExtensionsMap.put(getExtensionClass(name), getExtension(name));

实例演示

  • 扩展接口,及其实现类
@SPI("spring")
public interface FrameWork {
    @Adaptive
    String getName(URL url);
    String getInfo();
}

@Activate(value = {"name:dhy","age:18"},group = "test")
public class Guice implements FrameWork{
    @Override
    public String getName(URL url) {
        return "guice";
    }

    @Override
    public String getInfo() {
        return "google 开源的轻量级IOC框架";
    }
}

@Activate(value = {"name","sex"},group = "test")
public class Spring implements FrameWork{
    @Override
    public String getName(URL url) {
        return "spring";
    }

    @Override
    public String getInfo() {
        return "流行的Spring框架";
    }
}


@Activate(group = "prod")
public class SpringBoot implements FrameWork{
    @Override
    public String getName(URL url) {
        return "springBoot";
    }

    @Override
    public String getInfo() {
        return "自动化的SpringBoot框架";
    }
}
  • SPI文件内容
spring=com.adaptive.Spring
springBoot=com.adaptive.SpringBoot
guice=com.adaptive.Guice
  • 测试类
class ActivateTest {
    @Test
    void activateTest() {
        ApplicationModel applicationModel = ApplicationModel.defaultModel();
        ExtensionLoader<FrameWork> extensionLoader = applicationModel.getExtensionLoader(FrameWork.class);
        List<FrameWork> frameWorkList = extensionLoader.getActivateExtension(URL.valueOf("dubbo://127.0.0.1:80/?age=18"), "", "test");
        frameWorkList.forEach(frameWork -> {
            System.out.println(frameWork.getInfo());
        });
    }
}

在这里插入图片描述
大家可自行更多参数条件,测试其他分支。


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

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

相关文章

ChatGPT“保姆级教程”——手把手教你1分钟快速制作思维导图(Markmap/Xmind+Markdown)

目录 前言使用ChatGPT生成markdown格式主题Markmap Markdown使用Markmap生成思维导图 Xmind Markdown使用Xmind生成思维导图 建议其它资料下载 前言 思维导图是一种强大的工具&#xff0c;它可以帮助我们整理和展现复杂的思维结构&#xff0c;提升我们的思考能力和组织能力。…

chatgpt赋能python:Pythonsort逆序:使你的排序更有效

Python sort 逆序&#xff1a;使你的排序更有效 排序是计算机科学中最基本的操作之一&#xff0c;因为排序可以使计算机按某种有序的方式访问数据。Python sort 函数是一个非常有用的函数&#xff0c;它可以对列表或元组进行排序&#xff0c;但是&#xff0c;有时候我们需要对…

Win10 / 11新电脑最简单跳过联网激活和使用本地账户登录方法

跳过联网激活&#xff1a; OOBE界面直接按CtrlShiftF3进入审核模式。这样就可以直接进入系统进行一些硬件测试等&#xff0c;而不用联网激活导致新机无法退货。 需要注意的是&#xff0c;在审核模式下进行的一些操作都会保留&#xff0c;并不会在退出后自动还原&#xff01;安…

感谢飞书放过幕布!GPT-4平替Poe;100个GPT-4实战案例;AI绘画新手指南之SD篇;new Bing靠谱教程;AI生成视频摘要神器 | ShowMeAI日报

&#x1f440;日报合辑 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 『感谢飞书放过幕布』flomo 从字节收购幕布&#xff0c;时代变了 3月15日&#xff0c;flomo 浮墨笔记团队已与飞书初步达成全资收购幕布的合作意…

期末复习总结!!【MySQL】五种约束类型, 主键和外键的使用方式(重点)

文章目录 前言一、约束类型二、NOT NULL三、UNIQUE四、DEFAULT五、PRIMARY KEY(重点)1, 自增主键 六、FOREIGN KEY (重点)1, 插入数据2, 删除数据3, 关于外键约束下删除数据的思考 总结 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你: &#x1f4d5;…

数据库图书管理系统设计报告(基于软件工程)

文章目录 一、实验目的二、实验内容选题方向设计背景第一章 需求分析第一部分 系统设计目标第二部分 功能需求分析1&#xff0e;系统的功能描述2&#xff0e;主要的操作描述3&#xff0e;数据流图的演示&#xff08;1&#xff09;、顶层流程图&#xff08;2&#xff09;、第一层…

JAVA开发(记一次删除完全相同pgSQL数据库记录只保留一条)

进行数据管理时&#xff0c;无效数据可能会对生产力和决策质量造成严重的影响。如何发现和处理无效数据变得愈发重要。一起来唠唠你会如何处理无效数据吧~ 方向一&#xff1a;介绍无效数据的概念 最近遇到了pg数据库表中的大量数据重复了&#xff0c;需要删除其中的一条。一条…

数据库事务基本概念介绍

一、数据库事务是什么&#xff1f; 我们先不说数据库中的事务&#xff0c;我们看下百度对事务这个词汇的解释&#xff1a; 事务&#xff1a; 也就是说&#xff0c;事务就是&#xff0c;要做或者所做的事情 好的我们再联系一下生活中平常做的一些事情 例如 小 x 去 爱存不存的…

快速指南:在CentOS 7上安装Redis,构建高性能键值存储数据库

导航目录 1、Redis安装并使用1.1、下载安装包1.2、重命名和移动文件1.3、redis编译安装1.4、redis启动1.5、设置后台启动redis1.6、设置redis密码1.7、设置redis服务远程访问1.8、redis服务的客户端连接测试1.9、命令行使用1.10、清理缓存 Redis&#xff08;Remote Dictionary …

opencv_c++学习(二十一)

一、图像的轮廓检测 轮廓检测函数&#xff1a; findContours(lnputArray image, OutputArrayOfArrays contours,OutputArray hierarchy, int mode, int method, Point offset Point())image:输入图像&#xff0c;数据类型为CV_8U的单通道灰度图像或者二值化图像。contours:检…

mPython软件使用指南

①软件界面 一、软件界面的介绍 1.模式切换 硬件编程 Python3.6 Jupyter python3.6模式细节补充&#xff08;一般不使用该模式&#xff0c;此处可跳过&#xff09; Python3.6模式的界面 左侧指令分类栏 Python3.6模式的图形化指令分类分为&#xff1a; Python语法基础相关指令&…

Ajax 获取 JSON数据

文章目录 Ajax获取JSON数据 Ajax获取JSON数据 Ajax 全称“Asynchronous JavaScript and XML”&#xff0c;译为“异步 JavaScript 和 XML”&#xff0c;程序员们习惯称之为“阿贾克斯”&#xff0c;通过 Ajax 我们可以异步在服务器与客户端之间传递数据。在 Ajax 中&#xff0…

机器学习期末复习 贝叶斯分类器

先验概率与后验概率 先验概率&#xff1a;对于某一个概率事件&#xff0c;我们都会有基于自己已有的知识&#xff0c;对于这个概率事件会分别以什么概率出现各种结果会有一个预先的估计&#xff0c;而这个估计并未考虑到任何相关因素。 对于分类数据来说&#xff0c;先验概率就…

【lager】日志系统1:允许多个日志源向多个最终用户进行日志记录 windows cmake构建

【xerces】xerces-c-3.2.4 版本的cmake windows vs2022 构建 lager Light-weight Accumulator Gathering Efficiently in Real-time lagerLAGER(实时高效收集的轻量级累加器)是一个可靠的日志系统,旨在允许多个日志源向多个最终用户进行日志记录。该设计文档概述了整个系统的…

Android---APK 瘦身

在 APP 开发过程中&#xff0c;随着业务迭代&#xff0c;apk 体积逐渐变大。项目中累积的无用资源&#xff0c;未压缩的图片资源等&#xff0c;都为 apk 带来了不必要的体积增加。而 APK 的大小会影响应用加载速度、使用的内存量以及消耗的电量。 APK 结构 APK 文件由一个 Zip…

浅析Koa2中控制器

控制器 什么是控制器 拿到路由分配的任务&#xff0c;并执行 路由的功能是根据不同的 url, 来分配不同的任务。 控制器是拿到路由分配的任务并执行&#xff0c;是注册在路由中的中间件。 所以在 koa 中, 控制器也是一个中间件。 为什么要使用控制器 获取HTTP请求参数处理…

article-码垛机器人admas仿真

按照运动学仿真的类似步骤为机器人添加材料、运动副和关节驱动&#xff0c;给机器人手腕末端施加50N最大负载&#xff0c;仿真模型如图5-17。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AXYQVZPq-1684936426972)(data:image/svgxml;utf8, )] 图…

OpenGL之纹理

文章目录 什么是纹理加载与创建纹理stb_image.h加载并生成纹理 纹理环绕方式纹理过滤多级渐远纹理 纹理单元 什么是纹理 我们已经了解到&#xff0c;我们可以为每个顶点添加颜色来增加图形的细节&#xff0c;从而创建出有趣的图像。但是&#xff0c;如果想让图形看起来更真实&a…

unity制作一款塔防游戏

文章目录 介绍寻路系统怪物生成器制作3种初级炮台、3种升级炮台设置炮台属性选择炮台&#xff0c;添加监听事件炮弹追踪攻击敌人拖动鼠标实现相机视角转换鼠标光标放在cube上变色文字动画 介绍 关键技术&#xff1a; 寻路系统 生成怪物算法 粒子系统 line renderer制作追踪射线…

python基本操作1(速通版)

目录 一、input输入函数 二、格式化字符输出 三、函数的基本操作 1.return返回值的问题 2.参数传递 四、运算符 1.关系比较符 2.逻辑运算符 五、if语句 六、随机数 七、循环 1.while语句的基本应用 2.break语句 2.continue语句 3.猜拳游戏 4.三目运算符 6.for…