前言

在Dubbo中有Filter使用,对于Filter来说我们会遇到这样的问题,Filter自身有很多的实现,我们希望某种条件下使用A实现,另外情况下使用B实现,这个时候我们前面介绍@SPI和@Adaptive就不能满足我们要求了,这个时候我们就需要使用@Activate。
Activate注解表示一个扩展是否被激活(使用),可以放在类定义和方法上,Dubbo中用它在扩展类定义上,表示这个扩展实现激活条件和时机。

如何使用

  1. 自定义接口;
@SPI
public interface ActivateDemo {

    /**
     * 测试
     * @param msg
     * @return
     */
    String test(String msg);

}
  1. 实现接口,分别进行默认实现、多个组、排序、从URL获取值,多种方式的案例;
@Activate(group = {"default"})
public class DefaultActivateDemoImpl implements ActivateDemo {
    @Override
    public String test(String msg) {
        return msg;
    }
}

@Activate(group = {"groupA","groupB"})
public class ComposeGroupActivateDemoImpl implements ActivateDemo {

    @Override
    public String test(String msg) {
        return msg;
    }
}

@Activate(order = 1, group = {"order"})
public class Order1ActivateDemoImpl implements ActivateDemo{
    @Override
    public String test(String msg) {
        return msg;
    }
}

@Activate(order = 2, group = {"order"})
public class Order2ActivateDemoImpl implements ActivateDemo{
    @Override
    public String test(String msg) {
        return msg;
    }
}

@Activate(value = {"value"}, group = {"group"})
public class ValueAndGroupActivateDemoImpl implements ActivateDemo{
    @Override
    public String test(String msg) {
        return msg;
    }
}
  1. 在resources下新建META-INF/dubbo/internal文件夹,新建自己定义接口的全限定名文件名,名称以及内容可参考以下内容;

image.png
  1. 测试案例;
    public static void main(String[] args) {  
        
        ExtensionLoader<ActivateDemo> loader = ExtensionLoader.getExtensionLoader(ActivateDemo.class);
        URL url = URL.valueOf("test://localhost/test");
        List<ActivateDemo> list = loader.getActivateExtension(url, new String[]{}, "order");
        System.out.println(list.size());
        list.forEach(item -> System.out.println(item.getClass()));
    
    }
    public static void main(String[] args) {
        ExtensionLoader<ActivateDemo> loader = ExtensionLoader.getExtensionLoader(ActivateDemo.class);
        URL url = URL.valueOf("test://localhost/test");
        //注意这里要使用url接收,不能直接url.addParameter()
        url = url.addParameter("value", "test");
        List<ActivateDemo> list = loader.getActivateExtension(url, new String[]{"order1", "default"}, "group");
        System.out.println(list.size());
        list.forEach(item -> System.out.println(item.getClass()));
    }

源码分析

@Activate注解标注在扩展实现类上,有 group、value 以及 order 三个属性,三个属性作用如下:

  1. group 修饰的实现类可以列举为一种标签,标签用来区分是在 Provider 端被激活还是在 Consumer 端被激活;
  2. value 修饰的实现类只在 URL 参数中出现指定的 key 时才会被激活;
  3. order 用来确定扩展实现类的排序;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
   
    String[] group() default {};

    String[] value() default {};
    
    @Deprecated
    String[] before() default {};

    @Deprecated
    String[] after() default {};

    int order() default 0;
}

SPI在扩展类加载时候, loadClass() 方法会对 @Activate的注解类进行扫描,其中会将包含 @Activate 注解的实现类缓存到 cachedActivates 一个Map集合中,Key为扩展名,Value为@Activate注解;

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                           boolean overridden) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
        //判断类是否加载Adaptive注解
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz, overridden);
            //是否是扩展类,是的话就加入 cachedWrapperClasses 属性
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            //检测是否有默认构造起
            clazz.getConstructor();
            if (StringUtils.isEmpty(name)) {
                //未配置扩展名,自动生成,主要用于兼容java SPI的配置。
                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)) {
                //如果是自动激活的实现类,则加入到缓存
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    //存储Class到名字的映射关系
                    cacheName(clazz, n);
                    //存储名字到Class的映射关系
                    saveInExtensionClass(extensionClasses, clazz, n, overridden);
                }
            }
        }
    }

使用cachedActivates这个集合的地方是 getActivateExtension() ,关于此方法有4个重载函数,核心方法包含三个重要参数,URL中包含了配置信息,Values是配置中指定的扩展名,Group标签,下面是getActivateExtension的核心逻辑,首先就是获取默认的扩展集合,其次将扩获取到扩展类放到一个有序的集合中,按照顺序添加自定义扩展类的实现。

    public List<T> getActivateExtension(URL url, String key) {
        return getActivateExtension(url, key, null);
    }

    public List<T> getActivateExtension(URL url, String[] values) {
        return getActivateExtension(url, values, null);
    }

    public List<T> getActivateExtension(URL url, String key, String group) {
        String value = url.getParameter(key);
        return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
    }

    public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> activateExtensions = new ArrayList<>();
        // solve the bug of using @SPI's wrapper method to report a null pointer exception.
        // TreeMap进行排序
        TreeMap<Class, T> activateExtensionsMap = new TreeMap<>(ActivateComparator.COMPARATOR);
        Set<String> loadedNames = new HashSet<>();
        //传入的数组包装成为set
        Set<String> names = CollectionUtils.ofSet(values);
        //包装好的数据中判断不含"-default""
        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            //获取所有的加载类型
            getExtensionClasses();
            //cachedActivate 存储被@Activate修饰类型
            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                String name = entry.getKey();
                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;
                }
                //判断group是否匹配
                if (isMatchGroup(group, activateGroup)
                        //没有出现在values配置中的,即为默认激活的扩展实现
                        && !names.contains(name)
                        //通过"-"明确指定不激活该扩展实现
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        //检测URL中是否出现了指定的Key 
                        && isActive(activateValue, url)
                        //去重判断
                        && !loadedNames.contains(name)) {
                    //筛入treeMap中
                    activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
                    loadedNames.add(name);
                }
            }
            if (!activateExtensionsMap.isEmpty()) {
                activateExtensions.addAll(activateExtensionsMap.values());
            }
        }
        List<T> loadedExtensions = new ArrayList<>();
        for (String name : names) {
            //排除对应扩展名 不包含以-开始 以及 一+name
            if (!name.startsWith(REMOVE_VALUE_PREFIX)
                    && !names.contains(REMOVE_VALUE_PREFIX + name)) {
                if (!loadedNames.contains(name)) {
                    if (DEFAULT_KEY.equals(name)) {
                        if (!loadedExtensions.isEmpty()) {
                            activateExtensions.addAll(0, loadedExtensions);
                            loadedExtensions.clear();
                        }
                    } else {
                        //获取对应名字扩展
                        loadedExtensions.add(getExtension(name));
                    }
                    loadedNames.add(name);
                } else {
                    // If getExtension(name) exists, getExtensionClass(name) must exist, so there is no null pointer processing here.
                    String simpleName = getExtensionClass(name).getSimpleName();
                    logger.warn("Catch duplicated filter, ExtensionLoader will ignore one of them. Please check. Filter Name: " + name +
                            ". Ignored Class Name: " + simpleName);
                }
            }
        }
        if (!loadedExtensions.isEmpty()) {
            activateExtensions.addAll(loadedExtensions);
        }
        return activateExtensions;
    }

结束

欢迎大家点点关注,点点赞!

Dubbo-Activate实现原理的更多相关文章

  1. Spring Boot 中如何使用 Dubbo Activate 扩展点

    摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 『 公司的核心竞争力在于创新 – <启示录> 』 继续上一篇:< Spri ...

  2. 说一下Dubbo 的工作原理?注册中心挂了可以继续通信吗?

    面试题 说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?说说一次 rpc 请求的流程? 面试官心理分析 MQ.ES.Redis.Dubbo,上来先问你一些思考性的问题.原理,比如 kaf ...

  3. 1.说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?说说一次 rpc 请求的流程?

    作者:中华石杉 面试题 说一下的 dubbo 的工作原理?注册中心挂了可以继续通信吗?说说一次 rpc 请求的流程? 面试官心理分析 MQ.ES.Redis.Dubbo,上来先问你一些思考性的问题.原 ...

  4. dubbo的实现原理

    dubbo的介绍 dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的RPC实现服务的输出和输入功能,可以和Spring框架无缝集成. dubbo框架是基于Spring容器运 ...

  5. 分布式的几件小事(二)dubbo的工作原理

    1.dubbo的工作原理 ①整体设计 图例说明: 图中左边淡蓝背景的为服务消费方使用的接口,右边淡绿色背景的为服务提供方使用的接口,位于中轴线上的为双方都用到的接口. 图中从下至上分为十层,各层均为单 ...

  6. Dubbo优雅关机原理

    Dubbo是通过JDK的ShutdownHook来完成优雅停机的 所以如果用户使用 kill -9 PID 等强制关闭命令,是不会执行优雅停机的 只有通过 kill PID时,才会执行 原理: · 服 ...

  7. dubbo @Activate 注解使用和实现解析

    Activate注解表示一个扩展是否被激活(使用),可以放在类定义和方法上, dubbo用它在spi扩展类定义上,表示这个扩展实现激活条件和时机. 先看下定义: @Documented @Retent ...

  8. dubbo的工作原理

    dubbo工作原理 第一层:service层,接口层,给服务提供者和消费者来实现的 第二层:config层,配置层,主要是对dubbo进行各种配置的 第三层:proxy层,服务代理层,透明生成客户端的 ...

  9. dubbo系列七、dubbo @Activate 注解使用和实现解析

    一.用法 Activate注解表示一个扩展是否被激活(使用),可以放在类定义和方法上,dubbo用它在spi扩展类定义上,表示这个扩展实现激活条件和时机. @Activate(group = Cons ...

  10. 阿里dubbo服务注册原理解析

           阿里分布式服务框架 dubbo现在已成为了外面很多中小型甚至一些大型互联网公司作为服务治理的一个首选或者考虑方案,相信大家在日常工作中或多或少都已经用过或者接触过dubbo了.但是我搜了 ...

随机推荐

  1. day33-线程基础03

    线程基础03 6.用户线程和守护线程 用户线程:也叫工作线程,当线程的任务执行完或者通知方法结束.平时用到的普通线程均是用户线程,当在Java程序中创建一个线程,它就被称为用户线程 守护线程(Daem ...

  2. KingbaseES启动数据库失败后如何分析

    关键字: KingbaseES.sys_ctl.启动日志 一.KingbaseES数据库服务启动 1.1 数据库启动机制 1) 数据库通过sys_ctl工具手工启动数据库服务kingbase. 2) ...

  3. centos7设置时间和上海时区并进行同步

    1.设置时区(同步时间前先设置) timedatectl set-timezone Asia/Shanghai 2.安装组件 yum -y install ntp systemctl enable n ...

  4. 自定义View5 -塔防小游戏:第二篇防御塔随意放置

    第一篇:一个防御塔+多个野怪(简易版) 第二篇:防御塔随意放置 自定义View,处理事件分发,up,move,down. 第三篇:防御塔随意放置+多组野怪 第四篇:多波野怪 第五篇:杀死野怪获得金币 ...

  5. Java---Stream入门

    由于本文需要有一定的Lambda基础,所以如果不懂什么是Lambda的同学请移步:Java---Lambda 学习Stream的目的 函数式编程渐渐变成主流,而Stream是函数式编程的重点. 相对于 ...

  6. WSUS无法发现客户端

    这几天遇到一个问题,刚装好的WSUS服务器同步完补丁后,打算上线使用.挑了几个客户端,使用注册表配置了WSUS,但是迟迟无法在控制台上看到这些客户端.由于部分客户端不在域中,无法使用组策略配置,就写了 ...

  7. Java根据Freemarker模板生成Word文件

    1.  准备模板 模板 + 数据 = 模型 1.将准备好的Word模板文件另存为.xml文件(PS:建议使用WPS来创建Word文件,不建议用Office) 2.将.xml文件重命名为.ftl文件 3 ...

  8. 【全网最全】springboot整合JSR303参数校验与全局异常处理

    一.前言 我们在日常开发中,避不开的就是参数校验,有人说前端不是会在表单中进行校验的吗?在后端中,我们可以直接不管前端怎么样判断过滤,我们后端都需要进行再次判断,为了安全.因为前端很容易拜托,当测试使 ...

  9. 使用nginx代理nexus,不是/根路径

    location /nexus/ { proxy_pass http://192.168.0.218:8081/; proxy_set_header Host $host:$server_port; ...

  10. (四)JPA - JQPL 实现增删改查

    (一)JPA的快速入门 建议在需要使用时,看看之前的文章,先把环境搭起来. 5.JPQL JPQL和SQL 1.JPQL和SQL很像,查询关键字都是一样的 2.唯一的区别是:JPQL是面向对象的 JP ...