Dubbo3 源码解读-宋小生-7:Dubbo的SPI扩展机制之自动激活扩展Activate源码解析

本文涉及的产品
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: > Dubbo3 已经全面取代 HSF2 成为阿里的下一代服务框架,2022 双十一基于 Dubbo3 首次实现了关键业务不停推、不降级的全面用户体验提升,从技术上,大幅提高研发与运维效率的同时地址推送等关键资源利用率提升超 40%,基于三位一体的开源中间件体系打造了阿里在云上的单元化最佳实践和统一标准,同时将规模化实践经验与技术创新贡献开源社区,极大的推动了开源技术与标准的发展。> 本文是
Dubbo3 已经全面取代 HSF2 成为阿里的下一代服务框架,2022 双十一基于 Dubbo3 首次实现了关键业务不停推、不降级的全面用户体验提升,从技术上,大幅提高研发与运维效率的同时地址推送等关键资源利用率提升超 40%,基于三位一体的开源中间件体系打造了阿里在云上的单元化最佳实践和统一标准,同时将规模化实践经验与技术创新贡献开源社区,极大的推动了开源技术与标准的发展。

本文是 Dubbo 社区贡献者宋小生基于 Dubbo3 3.0.8 版本撰写的源码解析博客,在 Dubbo3 开源&内部技术栈统一的情况下,期望能对集团内的开发者了解 Dubbo3 背后的实现原理有所帮助。可点此查看 博客原文

本篇是宋小生系列 7/30 篇。同时,由 Dubbo3 团队领导的源码解读系列也正在进行中,感兴趣的同学可加入钉钉群了解详情: 28165003194

7.1 Activate扩展的说明

此注解对于使用给定条件自动激活某些扩展非常有用,例如:@Activate可用于在有多个实现时加载某些筛选器扩展。

  • group() 指定组条件。框架SPI定义了有效的组值。
  • value() 指定URL条件中的参数键。

SPI提供程序可以调用ExtensionLoader。getActivateExtension(URL、String、String)方法以查找具有给定条件的所有已激活扩展。

比如后面我们会说到的过滤器扩展对象的获取,如下通过调用getActivateExtension方法的代码:

 List<Filter> filters;
 filters = ScopeModelUtil.getExtensionLoader(Filter.class, moduleModels.get(0)).getActivateExtension(url, key, group);

7.2 获取自动激活扩展的源码

前面我们看了激活扩展是通过调用getActivateExtension方法来获取对象的,那接下来就来看下这个方法做了什么操作:

/**
* @param url 服务的url
* @param key  用于获取扩展点名称的url参数键 比如监听器:exporter.listener,过滤器:params-filter,telnet处理器:telnet
*/
 public List<T> getActivateExtension(URL url, String key) {
        return getActivateExtension(url, key, null);
    }

继续调用重载的方法

/**
     *  
     *
     * @param url   服务的url
     * @param key   用于获取扩展点名称的url参数键 比如监听器:exporter.listener,过滤器:params-filter,telnet处理器:telnet
     * @param group group 用于筛选的分组,比如过滤器中使用此参数来区分消费者使用这个过滤器还是提供者使用这个过滤器他们的group参数分表为consumer,provider
     * @return 已激活的扩展列表。
     */
 public List<T> getActivateExtension(URL url, String key, String group) {
         //从参数中获取url指定的值
        String value = url.getParameter(key);
        //调用下个重载的方法
        return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
    }

上面的重载方法都是用来转换参数的,下面这个方法才是真正的逻辑

 /**
     * 获取激活扩展.
     *
     * @param url    服务的url
     * @param values 这个value是扩展点的名字 当指定了时候会使用指定的名字的扩展
     * @param group  group 用于筛选的分组,比如过滤器中使用此参数来区分消费者使用这个过滤器还是提供者使用这个过滤器他们的group参数分表为consumer,provider
     * @return 获取激活扩展.
     */
    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集合,用来对扩展进行排序
        Map<Class<?>, T> activateExtensionsMap = new TreeMap<>(activateComparator);
        //初始化扩展名字,指定了扩展名字values不为空
        List<String> names = values == null ? new ArrayList<>(0) : asList(values);
        //扩展名字使用Set集合进行去重
        Set<String> namesSet = new HashSet<>(names);
        //参数常量是 -default  扩展名字是否不包含默认的
        if (!namesSet.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            //第一次进来肯定是没有缓存对象双重校验锁检查下
            if (cachedActivateGroups.size() == 0) {
                synchronized (cachedActivateGroups) {
                    // cache all extensions
                    if (cachedActivateGroups.size() == 0) {
                        //加载扩展类型对应的扩展类,这个具体细节参考源码或者《[Dubbo3.0.7源码解析系列]-5-Dubbo的SPI扩展机制与自适应扩展对象的创建与扩展文件的扫描源码解析》章节
                        getExtensionClasses();
                        for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                            String name = entry.getKey();
                            Object activate = entry.getValue();

                            String[] activateGroup, activateValue;
                            //遍历所有的activates列表获取group()和value()值
                            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;
                            }
                            //缓存分组值
                            cachedActivateGroups.put(name, new HashSet<>(Arrays.asList(activateGroup)));
                            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
            //遍历所有激活的扩展名字和扩展分组集合
            cachedActivateGroups.forEach((name, activateGroup) -> {
                //筛选当前扩展的扩展分组与激活扩展的扩展分组是否可以匹配
                if (isMatchGroup(group, activateGroup)
                    //不能是指定的扩展名字
                    && !namesSet.contains(name)
                    //也不能是带有 -指定扩展名字
                    && !namesSet.contains(REMOVE_VALUE_PREFIX + name)
                    //如果在Active注解中配置了value则当指定的键出现在URL的参数中时,激活当前扩展名。 
                    //如果未配置value属性则默认都是匹配的(cachedActivateValues中不存在对应扩展名字的缓存的时候默认为true)
                    && isActive(cachedActivateValues.get(name), url)) {
                    //缓存激活的扩展类型映射的扩展名字
                    activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
                }
            });
        }

        if (namesSet.contains(DEFAULT_KEY)) {
            // will affect order
            // `ext1,default,ext2` means ext1 will happens before all of the default extensions while ext2 will after them
            ArrayList<T> extensionsResult = new ArrayList<>(activateExtensionsMap.size() + names.size());
            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)) {
                        if (containsExtension(name)) {
                            extensionsResult.add(getExtension(name));
                        }
                    } else {
                        extensionsResult.addAll(activateExtensionsMap.values());
                    }
                }
            }
            return extensionsResult;
        } 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)) {
                        if (containsExtension(name)) {
                            activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
                        }
                    }
                }
            }
            return new ArrayList<>(activateExtensionsMap.values());
        }
    }

再来回顾下扫描扩展类型的时候,与激活扩展的相关扫描代码:
与激活注解关键的代码位置在这里ExtensionLoader的loadClass方法中
我来贴下loadClass方法核心的代码:

if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz, overridden);
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            if (StringUtils.isEmpty(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 (ArrayUtils.isNotEmpty(names)) {
            //位置在这里其他地方就不标记注释了,前面判断了如果不是Adaptive也不是Wrapper类型则我们可以来判断是否为Activate 类型如果是的话调用cacheActivateClass方法将扩展缓存进cachedActivates缓存中
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);
                }
            }
        }
private void cacheActivateClass(Class<?> clazz, String name) {
        Activate activate = clazz.getAnnotation(Activate.class);
        if (activate != null) {
            //注解存在则加入激活注解缓存
            cachedActivates.put(name, activate);
        } else {
            // support com.alibaba.dubbo.common.extension.Activate
            com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
            if (oldActivate != null) {
                cachedActivates.put(name, oldActivate);
            }
        }
    }
目录
相关文章
|
29天前
|
XML Java 数据格式
深度解析 Spring 源码:从 BeanDefinition 源码探索 Bean 的本质
深度解析 Spring 源码:从 BeanDefinition 源码探索 Bean 的本质
30 3
|
1月前
|
Java 开发者 Spring
深入解析这两种扩展机制的工作原理和用法
深入解析这两种扩展机制的工作原理和用法
|
14天前
|
存储 安全 Java
深度长文解析SpringWebFlux响应式框架15个核心组件源码
以上是Spring WebFlux 框架核心组件的全部介绍了,希望可以帮助你全面深入的理解 WebFlux的原理,关注【威哥爱编程】,主页里可查看V哥每天更新的原创技术内容,让我们一起成长。
|
15天前
|
关系型数据库 分布式数据库 数据库
PolarDB-X源码解析:揭秘分布式事务处理
【7月更文挑战第3天】**PolarDB-X源码解析:揭秘分布式事务处理** PolarDB-X,应对大规模分布式事务挑战,基于2PC协议确保ACID特性。通过预提交和提交阶段保证原子性与一致性,使用一致性快照隔离和乐观锁减少冲突,结合故障恢复机制确保高可用。源码中的事务管理逻辑展现了优化的分布式事务处理流程,为开发者提供了洞察分布式数据库核心技术的窗口。随着开源社区的发展,更多创新实践将促进数据库技术进步。
21 3
|
29天前
|
XML Java 数据格式
深度解析 Spring 源码:揭秘 BeanFactory 之谜
深度解析 Spring 源码:揭秘 BeanFactory 之谜
22 1
|
1月前
|
JavaScript 前端开发 Java
【JavaScript】ECMAS6(ES6)新特性概览(二):解构赋值、扩展与收集、class类全面解析
【JavaScript】ECMAS6(ES6)新特性概览(二):解构赋值、扩展与收集、class类全面解析
25 2
|
12天前
|
传感器 芯片
嵌入式通信协议全解析:SPI、I²C、UART详解(附带面试题)
通信是指人与人或人与自然之间通过某种行为或媒介进行的信息交流与传递。从广义上来说,通信是指需要信息的双方或多方在不违背各自意愿的情况下采用任意方法、任意媒质,将信息从某方准确安全地传送到另方。在出现电波传递通信后,通信被单一解释为信息的传递,是指由一地向另一地进行信息的传输与交换,其目的是传输消息。通信方式包括利用“电”来传递消息的电信,这种通信具有迅速、准确、可靠等特点,且几乎不受时间、地点、空间、距离的限制,因而得到了飞速发展和广泛应用。
|
13天前
|
传感器 编解码 API
【STM32开发入门】温湿度监测系统实战:SPI LCD显示、HAL库应用、GPIO配置、UART中断接收、ADC采集与串口通信全解析
SPI(Serial Peripheral Interface)是一种同步串行通信接口,常用于微控制器与外围设备间的数据传输。SPI LCD是指使用SPI接口与微控制器通信的液晶显示屏。这类LCD通常具有较少的引脚(通常4个:MISO、MOSI、SCK和SS),因此在引脚资源有限的系统中非常有用。通过SPI协议,微控制器可以向LCD发送命令和数据,控制显示内容和模式。
|
16天前
|
前端开发 开发者
深入解析Vite.js源码
【7月更文挑战第1天】Vite.js 深入解析:以其无bundle开发、动态ES模块加载提升开发效率;本地HTTP服务器配合WebSocket实现热更新;按需加载减少资源占用;预构建优化生产环境性能;基于Rollup的插件系统增强灵活性。Vite,一个创新且高效的前端构建工具。
21 0
|
29天前
|
Java Spring
深入解析Spring源码,揭示JDK动态代理的工作原理。
深入解析Spring源码,揭示JDK动态代理的工作原理。
22 0

热门文章

最新文章

推荐镜像

更多