Dubbo自激活扩展实现原理分析

本文深入探讨了Dubbo的自激活扩展机制,详细解释了如何根据分组和URL参数,结合@Activate注解选择合适的扩展实例。通过分析Apache Dubbo 2.7.8的源码,揭示了激活扩展在过滤器(Filter)应用中的作用,帮助理解其在服务拦截和定制功能实现中的重要性。

Dubbo的自激活扩展说明

  • Dubbo的激活扩展是指根据分组和url参数中的key,结合扩展类上的注解Activate,生成符合匹配条件的扩展实例,得到一个实例集合。

Dubbo中的应用场景

  • 激活扩展在Dubbo中一个典型的应用场景就是过滤器(Filter), 在服务端收到请求之后,经过一系列过滤器去拦截请求,做一些处理工作,然后在真正去调用实现类。

@Activate注解说明

/**
 * 激活。这个注解对于使用给定的条件自动激活特定的扩展非常有用,例如:当有多个实现时,@Activate可以用于加载特定的过滤器扩展。
 * group()指定组条件。框架SPI定义了有效的组值。
 * value()指定URL条件中的参数key。
 * SPI提供程序可以调用ExtensionLoader.getActivateExtension(URL, String, String)来查找给定条件下所有激活的扩展。
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    /**
     * Activate the current extension when one of the groups matches. The group passed into
     * {@link ExtensionLoader#getActivateExtension(URL, String, String)} will be used for matching.
     * 当其中一个组匹配时激活当前扩展。
     * @return group names to match
     * @see ExtensionLoader#getActivateExtension(URL, String, String)
     */
    String[] group() default {};

    /**
     * Activate the current extension when the specified keys appear in the URL's parameters.
     * 当指定的键出现在URL的参数中时激活当前的扩展。
     * 例如,给定@Activate(“cache, validation”),当前的扩展只会在URL的参数中出现cache或validation时返回。
     *
     * @return URL parameter keys
     * @see ExtensionLoader#getActivateExtension(URL, String)
     * @see ExtensionLoader#getActivateExtension(URL, String, String)
     */
    String[] value() default {};

    /**
     * Relative ordering info, optional
     * Deprecated since 2.7.0
     * 关联顺序,是可选的,2.7.0以后被废弃
     * @return extension list which should be put before the current one
     */
    @Deprecated
    String[] before() default {};

    /**
     * Relative ordering info, optional
     * Deprecated since 2.7.0
     * 关联顺序,是可选的,2.7.0以后被废弃
     * @return extension list which should be put after the current one
     */
    @Deprecated
    String[] after() default {};

    /**
     * Absolute ordering info, optional
     * 绝对的顺序信息,可选的参数
     * @return absolute ordering info
     */
    int order() default 0;
}

源码分析(apache dubbo 2.7.8)

  • ExtensionLoader关于getActivateExtension有4个重载方法,其中1,2,3最终都会调用方法4,所以我们直接分析方法4的逻辑
1.  public List<T> getActivateExtension(URL url, String key) {
        return getActivateExtension(url, key, null);
    }
    
2.  public List<T> getActivateExtension(URL url, String[] values) {
        return getActivateExtension(url, values, null);
    }
    
3.  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);
    }
    
    /**
     * Get activate extensions. 获取激活的扩展实例
     *
     * @param url    url
     * @param values extension point names  扩展点名称数组
     * @param group  group                  分组
     * @return extension list which are activated 返回被激活的扩展类集合
     * @see org.apache.dubbo.common.extension.Activate
     */
4.  public List<T> getActivateExtension(URL url, String[] values, String group) {
        List<T> activateExtensions = new ArrayList<>();
        List<String> names = values == null ? new ArrayList<>(0) : asList(values);
        // 等同于 !names.contains("-default")
        // 一般情况下,传入-default代表忽略@Activate注解上的属性,以传入的values来判断是否生成实例,'-xxx',表示忽略扩展名为xxx的扩展类
        if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
            // 获取所有的扩展Class
            getExtensionClasses();
            // cachedActivates缓存的是name到Activate的映射
            for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                // 扩展类的名称,即配置文件中配置的名称
                String name = entry.getKey();
                // 注解Activate的信息
                Object activate = entry.getValue();
                String[] activateGroup, activateValue;
                if (activate instanceof Activate) {
                    // 获取activate注解上group的值
                    activateGroup = ((Activate) activate).group();
                    // 获取activate注解上value的值
                    activateValue = ((Activate) activate).value();
                // 兼容历史alibaba包的注解
                } 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;
                }
                // 这里是判断是否符合匹配条件
                // 1. isMatchGroup(group, activateGroup) 匹配分组, 注解中的groups数组是否包含参数中的group
                // 2. !names.contains(name)    names不能包含扩展类名称
                // 3. !names.contains(REMOVE_VALUE_PREFIX + name) names不能包含'-扩展类名称'
                // 4. isActive(activateValue, url))  这里则根据配置的value值,和url中参数,是否匹配
                if (isMatchGroup(group, activateGroup)
                        && !names.contains(name)
                        && !names.contains(REMOVE_VALUE_PREFIX + name)
                        && isActive(activateValue, url)) {
                    // 生成扩展实例,加入到集合中
                    activateExtensions.add(getExtension(name));
                }
            }
            // 排序(一般情况下,按照order排序)
            activateExtensions.sort(ActivateComparator.COMPARATOR);
        }
        // 定义一个加载扩展的集合
        List<T> loadedExtensions = new ArrayList<>();
        // 遍历其中的每一个name
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            // 如果name是没有以 '-' 作为开始,并且集合中也不包含'-name'
            if (!name.startsWith(REMOVE_VALUE_PREFIX)
                    && !names.contains(REMOVE_VALUE_PREFIX + name)) {
                // 如果name的值为default, 
                if (DEFAULT_KEY.equals(name)) { // default
                    // 已加载扩展的集合不为空
                    if (!loadedExtensions.isEmpty()) {
                        // 将已扩展的实例集合加入到activateExtensions集合中,并清空loadedExtensions集合
                        activateExtensions.addAll(0, loadedExtensions);
                        loadedExtensions.clear();
                    }
                } else {
                    // 获取到扩展实例后,添加到loadedExtensions集合中
                    loadedExtensions.add(getExtension(name));
                }
            }
        }
        // 遍历完成后,如果loadedExtensions不为空,则添加到activateExtensions集合中,并返回
        if (!loadedExtensions.isEmpty()) {
            activateExtensions.addAll(loadedExtensions);
        }
        return activateExtensions;
    }
    
    /**
     * 检查是否匹配分组
     */
    private boolean isMatchGroup(String group, String[] groups) {
        if (StringUtils.isEmpty(group)) {
            return true;
        }
        if (groups != null && groups.length > 0) {
            for (String g : groups) {
                if (group.equals(g)) {
                    return true;
                }
            }
        }
        return false;
    }
    
    /**
     * 是否激活
     */
    private boolean isActive(String[] keys, URL url) {
        
        if (keys.length == 0) {
            return true;
        }
        for (String key : keys) {
            // @Active(value="key1:value1, key2:value2")
            String keyValue = null;
            if (key.contains(":")) {
                String[] arr = key.split(":");
                key = arr[0];
                keyValue = arr[1];
            }
            
            // 检查url中的参数是否包含key或者以.key结尾,然后在检查keyValue与url参数中对应key的value是否相等
            for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
                String k = entry.getKey();
                String v = entry.getValue();
                if ((k.equals(key) || k.endsWith("." + key))
                        && ((keyValue != null && keyValue.equals(v)) || (keyValue == null && ConfigUtils.isNotEmpty(v)))) {
                    return true;
                }
            }
        }
        return false;
    }

总结

  • Dubbo的自激活扩展还是比较容易理解的,是根据方法参数,结合扩展类上Activate注解,过滤得到一个匹配的扩展实现集合,有了这个集合,我们可以实现一些定制化功能,比如拦截请求,监听处理等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值