7-Dubbo Extension Activate 自动激活扩展源码解析

作者: vnjohn / 发表于 2025-05-21 / 专栏: Dubbo 3.3

Dubbo3, 源码

7.1 Activate 扩展说明

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

  1. group:指定组条件,框架 SPI 定义了有效的组值 CommonConstants.PROVIDER / CommonConstants.CONSUMER
  2. value:指定 URL 条件中的参数键

SPI 提供程序可以调用 ExtensionLoader#getActivateExtension(URL url, String key, String group) 方法查找具有给定条件的所有已激活扩展,比如:DefaultFilterChainBuilder#buildInvokerChain 过滤器扩展对象的获取,通过调用 getActivateExtension 方法的代码

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

7.2 获取自动激活扩展

前面激活扩展是通过调用 getActivateExtension 方法来获取对象的,那么接下来就来看下该方法是如何操作的

/**
 * This is equivalent to {@code getActivateExtension(url, url.getParameter(key).split(","), 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);
}

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

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)
    : Arrays.stream(values).map(StringUtils::trim).collect(Collectors.toList());
  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) {
          // 加载扩展类型对应的扩展类
          // 具体的方法执行流程可以看 《5-Dubbo Extension Adaptive 自适应扩展源码解析》章 5.2 小节内容
          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 (Dubbo2CompactUtils.isEnabled()
                       && Dubbo2ActivateUtils.isActivateLoaded()
                       && Dubbo2ActivateUtils.getActivateClass().isAssignableFrom(activate.getClass())) {
              activateGroup = Dubbo2ActivateUtils.getGroup((Annotation) activate);
              activateValue = Dubbo2ActivateUtils.getValue((Annotation) activate);
            } 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 (String name : names) {
      if (name.startsWith(REMOVE_VALUE_PREFIX) || namesSet.contains(REMOVE_VALUE_PREFIX + name)) {
        continue;
      }
      if (DEFAULT_KEY.equals(name)) {
        extensionsResult.addAll(activateExtensionsMap.values());
        continue;
      }
      if (containsExtension(name)) {
        extensionsResult.add(getExtension(name));
      }
    }
    return extensionsResult;
  } else {
    // add extensions, will be sorted by its order
    for (String name : names) {
      if (name.startsWith(REMOVE_VALUE_PREFIX) || namesSet.contains(REMOVE_VALUE_PREFIX + name)) {
        continue;
      }
      if (DEFAULT_KEY.equals(name)) {
        continue;
      }
      if (containsExtension(name)) {
        activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
      }
    }
    return new ArrayList<>(activateExtensionsMap.values());
  }
}

具体解释一下源码中是如何解析 @Active 注解 value、group 中,以如下配置为例:

@Activate(group = {CommonConstants.PROVIDER, CommonConstants.CONSUMER}, value = {"key1:value1", "key2:value2"})

cachedActivateGroups Map 集合会有一条元素,比如在 file 文件中配置的 key 为 demo,那么整个集合会是:{demo=[provider, consumer]}

keyPairs 二维数组集合会有两个元素,如上举例整个集合会是:[[key1, value1], [key2, value2]]

遍历的 cachedActivates 集合元素,其实是加载扩展类时已经设置进去了,回顾一下来自 5.2.3 小节的 ExtensionLoader#loadClass 方法,扫描扩展类型时与激活扩展的相关扫描代码,这里 loadClass 方法关于这块核心代码:

// 无自适应注解,也没有构造器是扩展类型参数, name 在扩展文件中找到的话那就是等号前面那个值
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);
    // 将扩展类型加入结果集合 extensionClasses 中,不允许覆盖的话出现相同名字扩展将抛出异常
    saveInExtensionClass(extensionClasses, clazz, n, overridden);
  }
}

主要就是看 cacheActivateClass 方法的判断逻辑

private void cacheActivateClass(Class<?> clazz, String name) {
  Activate activate = clazz.getAnnotation(Activate.class);
  // 注解存在则加入激活注解缓存
  if (activate != null) {
    cachedActivates.put(name, activate);
  } else if (Dubbo2CompactUtils.isEnabled() && Dubbo2ActivateUtils.isActivateLoaded()) {
    // support com.alibaba.dubbo.common.extension.Activate
    Annotation oldActivate = clazz.getAnnotation(Dubbo2ActivateUtils.getActivateClass());
    if (oldActivate != null) {
      cachedActivates.put(name, oldActivate);
    }
  }
}

主要在两种条件下该扩展类会被自动激活扩展

  1. 当前扩展类上存在 @Activate 注解修饰
  2. 为了适配 Dubbo 2.x 版本逻辑,若扩展类上使用 com.alibaba.dubbo.common.extension.Activate 注解修饰,也可以被自动激活

7.3 Activate 激活扩展类加载流程图

在这里插入图片描述

参考文献

https://www.ktyhub.com/zh/chapter_dubbo/7-extension-activate/

vnjohn

作者

vnjohn

后端研发工程师。喜欢探索新技术,空闲时也折腾 AIGC 等效率工具。 可以在 GitHub 关注我了解更多,也可以加我微信(vnjohn) 与我交流。