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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: > 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);
            }
        }
    }
目录
相关文章
|
5月前
|
缓存 Dubbo Java
Dubbo 第三节_ Dubbo的可扩展机制SPI源码解析
Dubbo会对DubboProtocol对象进⾏依赖注⼊(也就是⾃动给属性赋值,属性的类型为⼀个接⼝,记为A接⼝),这个时候,对于Dubbo来说它并不知道该给这个属性赋什么值,换句话说,Dubbo并不知道在进⾏依赖注⼊时该找⼀个什么的的扩展点对象给这个属性,这时就会预先赋值⼀个A接⼝的⾃适应扩展点实例,也就是A接⼝的⼀个代理对象。在调⽤getExtension去获取⼀个扩展点实例后,会对实例进⾏缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。Protocol是⼀个接。但是,不是只要在⽅法上加了。
|
10天前
|
缓存 负载均衡 Dubbo
Dubbo技术深度解析及其在Java中的实战应用
Dubbo是一款由阿里巴巴开源的高性能、轻量级的Java分布式服务框架,它致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
31 6
|
2月前
|
缓存 Dubbo Java
Dubbo线程模型设计解析
该文章主要介绍了Dubbo线程模型的设计解析,包括Dubbo作为一个支持大量并发请求的网络框架的特点,以及其线程模型的工作原理。
|
5月前
|
负载均衡 算法
Dubbo-负载均衡原理解析(1),一个本科渣渣是怎么逆袭从咸鱼到Offer收割机的
Dubbo-负载均衡原理解析(1),一个本科渣渣是怎么逆袭从咸鱼到Offer收割机的
|
5月前
|
XML 缓存 Dubbo
Dubbo的魔法之门:深入解析SPI扩展机制【八】
Dubbo的魔法之门:深入解析SPI扩展机制【八】
68 0
|
5月前
|
Dubbo Java 应用服务中间件
微服务框架(十七)Dubbo协议及编码过程源码解析
  此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。   本文为Dubbo协议、线程模型、和其基于Netty的NIO异步通讯机制及源码
|
5月前
|
Dubbo Java 应用服务中间件
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
|
2月前
|
Dubbo Java 应用服务中间件
💥Spring Cloud Dubbo火爆来袭!微服务通信的终极利器,你知道它有多强大吗?🔥
【8月更文挑战第29天】随着信息技术的发展,微服务架构成为企业应用开发的主流模式,而高效的微服务通信至关重要。Spring Cloud Dubbo通过整合Dubbo与Spring Cloud的优势,提供高性能RPC通信及丰富的生态支持,包括服务注册与发现、负载均衡和容错机制等,简化了服务调用管理并支持多种通信协议,提升了系统的可伸缩性和稳定性,成为微服务通信领域的优选方案。开发者仅需关注业务逻辑,而无需过多关心底层通信细节,使得Spring Cloud Dubbo在未来微服务开发中将更加受到青睐。
67 0
|
9天前
|
Dubbo 应用服务中间件 Apache
Star 4w+,Apache Dubbo 3.3 全新发布,Triple X 领衔,开启微服务通信新时代
在 Apache Dubbo 突破 4w Star 之际,Apache Dubbo 团队正式宣布,Dubbo 3.3 正式发布!作为全球领先的开源微服务框架,Dubbo 一直致力于为开发者提供高性能、可扩展且灵活的分布式服务解决方案。此次发布的 Dubbo 3.3,通过 Triple X 的全新升级,突破了以往局限,实现了对南北向与东西向流量的全面支持,并提升了对云原生架构的友好性。
|
2月前
|
负载均衡 Dubbo 应用服务中间件
框架巨擘:Dubbo如何一统异构微服务江湖,成为开发者的超级武器!
【8月更文挑战第8天】在软件开发中,微服务架构因灵活性和可扩展性备受欢迎。面对异构微服务的挑战,Apache Dubbo作为高性能Java RPC框架脱颖而出。它具备服务注册与发现、负载均衡及容错机制等核心特性,支持多种通信协议和序列化方式,能有效连接不同技术栈的微服务。Dubbo的插件化设计保证了面向未来的扩展性,使其成为构建稳定高效分布式系统的理想选择。
40 5
下一篇
无影云桌面