乐趣区

9Dubbo的SPI机制分析5Activate详解

1、Dubbo 的 @Activate 例子

@SPI
public interface ActivateExt {String echo(String msg);
}
@Activate(group = {"default"})
public class ActivateExtImpl1 implements ActivateExt {
    @Override
    public String echo(String msg) {return msg;}
}

@Activate(group = {"group1", "group2"})
public class GroupActivateExtImpl implements ActivateExt {

    @Override
    public String echo(String msg) {return msg;}
}
@Activate(order = 2, group = {"order"})
public class OrderActivateExtImpl1 implements ActivateExt {

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

@Activate(order = 1, group = {"order"})
public class OrderActivateExtImpl2 implements ActivateExt {

    @Override
    public String echo(String msg) {return msg;}
}
@Activate(value = {"value"}, group = {"group"})
public class ValueActivateExtImpl implements ActivateExt {

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

在 resources 下新建 META-INF/dubbo/internal 文件夹,新建 com.alibaba.dubbo.demo.provider.activate.ActivateExt,即接口的 全限定名,文件内容为:

group=com.alibaba.dubbo.demo.provider.activate.impl.GroupActivateExtImpl
key=com.alibaba.dubbo.demo.provider.activate.impl.ValueActivateExtImpl
order1=com.alibaba.dubbo.demo.provider.activate.impl.OrderActivateExtImpl1
order2=com.alibaba.dubbo.demo.provider.activate.impl.OrderActivateExtImpl2
com.alibaba.dubbo.demo.provider.activate.impl.ActivateExtImpl1

下面是几个单元测试及其测试结果,观察测试结果我们可以得出以下结论:
1) 如果 loader.getActivateExtension(URL url, String[] values, String group)中的 group 参数能够匹配到, 就是我们要选择的,test1 中根据 group=default 匹配到了 ActivateExtImpl1, 它也是在此条件下需要激活的
2) @Activate 中的 value 是第二层校验参数, 第一层是 group, 在 group 校验通过的情况下, 如果 URL 中的 key 参数与类 @Activate 注解中的 value 参数一致且 key 参数对应的值不为空, 则才会被选中
3) order 越小, 优先级越高

/**
 * 1
 * class com.alibaba.dubbo.demo.provider.activate.impl.ActivateExtImpl1
 */
@Test
public void test1(){ExtensionLoader<ActivateExt> loader = ExtensionLoader.getExtensionLoader(ActivateExt.class);
    URL url = URL.valueOf("test://localhost/test");
    List<ActivateExt> list = loader.getActivateExtension(url, new String[]{}, "default");
    System.out.println(list.size());
    list.forEach(item -> System.out.println(item.getClass()));
}
/**
 * 1
 * class com.alibaba.dubbo.demo.provider.activate.impl.GroupActivateExtImpl
 */
@Test
public void test2(){ExtensionLoader<ActivateExt> loader = ExtensionLoader.getExtensionLoader(ActivateExt.class);
    URL url = URL.valueOf("test://localhost/test");
    List<ActivateExt> list = loader.getActivateExtension(url, new String[]{}, "group1");
    System.out.println(list.size());
    list.forEach(item -> System.out.println(item.getClass()));
}
/**
 * 2
 * class com.alibaba.dubbo.demo.provider.activate.impl.OrderActivateExtImpl1
 * class com.alibaba.dubbo.demo.provider.activate.impl.ValueActivateExtImpl
 */
@Test
public void test3(){ExtensionLoader<ActivateExt> loader = ExtensionLoader.getExtensionLoader(ActivateExt.class);
    URL url = URL.valueOf("test://localhost/test");
    // 注意这里要使用 url 接收, 不能直接 url.addParameter()
    url = url.addParameter("value", "test");
    List<ActivateExt> list = 
                   loader.getActivateExtension(url, new String[]{"order1", "default"}, "group");
    System.out.println(list.size());
    list.forEach(item -> System.out.println(item.getClass()));
}
/**
 * 2
 * class com.alibaba.dubbo.demo.provider.activate.impl.OrderActivateExtImpl2
 * class com.alibaba.dubbo.demo.provider.activate.impl.OrderActivateExtImpl1
 */
@Test
public void test4(){ExtensionLoader<ActivateExt> loader = ExtensionLoader.getExtensionLoader(ActivateExt.class);
    URL url = URL.valueOf("test://localhost/test");
    List<ActivateExt> list = loader.getActivateExtension(url, new String[]{}, "order");
    System.out.println(list.size());
    list.forEach(item -> System.out.println(item.getClass()));
}

2、源码分析

public void test3(){ExtensionLoader<ActivateExt> loader = ExtensionLoader.getExtensionLoader(ActivateExt.class);
    URL url = URL.valueOf("test://localhost/test");
    // 注意这里要使用 url 接收, 不能直接 url.addParameter()
    url = url.addParameter("value", "test");
    List<ActivateExt> list = 
                   loader.getActivateExtension(url, new String[]{"order1", "default"}, "group");
    System.out.println(list.size());
    list.forEach(item -> System.out.println(item.getClass()));
}
public List<T> getActivateExtension(URL url, String[] values, String group) {List<T> exts = new ArrayList<T>();
    // 将传递进来的 values 包装成 List 类型的 names
    List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values);
    // 包装好的数据中不含 "-default"
    if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
        // 获取这个类型的数据的所有扩展信息
        getExtensionClasses();
        for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
            // 获取拓展的名称, 在配置文件中配置的 key=value 中的 key 值
            String name = entry.getKey();
            // 获取注解
            Activate activate = entry.getValue();
            // 判断 group 是否匹配,group 是程序传进来的值, 如果没有设定则返回 true 表示匹配,
            // 如果设定了需要进行比较匹配
            if (isMatchGroup(group, activate.group())) {
                // 获取 key 值即 name 对应的实例
                T ext = getExtension(name);
                if (!names.contains(name)
                        && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
                        && isActive(activate, url)) {exts.add(ext);
                }
            }
        }
        // 排序
        Collections.sort(exts, ActivateComparator.COMPARATOR);
    }
    List<T> usrs = new ArrayList<T>();
    for (int i = 0; i < names.size(); i++) {String name = names.get(i);
        if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)
                && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {if (Constants.DEFAULT_KEY.equals(name)) {
                // 如果 name 是 default, 就将 usrs 加入 exts 头部并清空 usrs
                if (!usrs.isEmpty()) {exts.addAll(0, usrs);
                    usrs.clear();}
            } else {
                // 获取 name 对应的拓展并加入 usrs
                T ext = getExtension(name);
                usrs.add(ext);
            }
        }
    }
    if (!usrs.isEmpty()) {exts.addAll(usrs);
    }
    return exts;
}

上述代码中用到的 cachedActivates 是在 getExtensionClasses()方法中赋值的,具体可以参考:Dubbo 的 SPI 机制分析 1 -SPI 加载 class

Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {cachedActivates.put(names[0], activate);
}
// 判断 group 是否匹配, 很好理解
private boolean isMatchGroup(String group, String[] groups) {if (group == null || group.length() == 0) {return true;}
    if (groups != null && groups.length > 0) {for (String g : groups) {if (group.equals(g)) {return true;}
        }
    }
    return false;
}
// 将 url 中的键 key 与 Activate 注解中的 value 值做匹配, 并且 key 所对应的 value 值不能为空
private boolean isActive(Activate activate, URL url) {String[] keys = activate.value();
    if (keys.length == 0) {return true;}
    for (String key : keys) {for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {String k = entry.getKey();
            String v = entry.getValue();
            if ((k.equals(key) || k.endsWith("." + key))
                    && ConfigUtils.isNotEmpty(v)) {return true;}
        }
    }
    return false;
}

注意,如果是以下这种形式,传入的 values 中有 -default,则所有的匹配可以通过的 @Activate 都不会被激活,只有 values 中指定的可以被激活。如果传入了“-”符合开头的扩展类名,则该扩展点也不会被激活。

// 输出 class com.alibaba.dubbo.demo.provider.activate.impl.OrderActivateExtImpl1
ExtensionLoader<ActivateExt> loader = ExtensionLoader.getExtensionLoader(ActivateExt.class);
URL url = URL.valueOf("test://localhost/test");
// 注意这里要使用 url 接收, 不能直接 url.addParameter()
url = url.addParameter("value", "test");
List<ActivateExt> list = loader.getActivateExtension(url, new String[]{"order1", "-default"}, "group");
System.out.println(list.size());
list.forEach(item -> System.out.println(item.getClass()));
退出移动版