Dubbo SPI之Activate详解

前期准备

一. 增加pom


   com.alibaba
   dubbo
   2.5.3

二. 添加代码

重要声明:本节演示的源码来自于网络的一片文章,看例子讲解的很透彻,很能说明问题就直接引用过来了,没法注明出处,如果有知道的请和我联系下,我添加上——尊重原创,从我做起

1. shuqi.dubbotest.spi.adaptive.AdaptiveExt2 作为需要被扩展的接口,注意要加上@SPI注解

package shuqi.dubbotest.spi.activate;

import com.alibaba.dubbo.common.extension.SPI;

@SPI
public interface ActivateExt1 {
    String echo(String msg);
}


2. 上面接口的五个实现类

a. shuqi.dubbotest.spi.activate.ActivateExt1Impl1
package shuqi.dubbotest.spi.activate;

import com.alibaba.dubbo.common.extension.Activate;

/**
 * @author linyang on 18/4/20.
 */
@Activate(group = {"default_group"})
public class ActivateExt1Impl1 implements ActivateExt1 {

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

b. shuqi.dubbotest.spi.activate.GroupActivateExtImpl
package shuqi.dubbotest.spi.activate;

import com.alibaba.dubbo.common.extension.Activate;

/**
 * @author linyang on 18/4/20.
 */
@Activate(group = {"group1", "group2"})
public class GroupActivateExtImpl implements ActivateExt1 {
    public String echo(String msg) {
        return msg;
    }
}
c. shuqi.dubbotest.spi.activate.OrderActivateExtImpl1
package shuqi.dubbotest.spi.activate;

import com.alibaba.dubbo.common.extension.Activate;

/**
 * @author linyang on 18/4/20.
 */
@Activate(order = 2, group = {"order"})
public class OrderActivateExtImpl1 implements ActivateExt1 {
    public String echo(String msg) {
        return msg;
    }
}
d. shuqi.dubbotest.spi.activate.OrderActivateExtImpl2
package shuqi.dubbotest.spi.activate;

import com.alibaba.dubbo.common.extension.Activate;

/**
 * @author linyang on 18/4/20.
 */
@Activate(order = 1, group = {"order"})
public class OrderActivateExtImpl2 implements ActivateExt1 {
    public String echo(String msg) {
        return msg;
    }
}
e. shuqi.dubbotest.spi.activate.ValueActivateExtImpl
package shuqi.dubbotest.spi.activate;

import com.alibaba.dubbo.common.extension.Activate;

/**
 * @author linyang on 18/4/20.
 */
@Activate(value = {"value1"}, group = {"value"})
public class ValueActivateExtImpl implements ActivateExt1 {
    public String echo(String msg) {
        return msg;
    }
}

3. 在Resource目录下,添加/META-INF/dubbo/internal/shuqi.dubbotest.spi.activate.ActivateExt1文件,里面的内容

group=shuqi.dubbotest.spi.activate.GroupActivateExtImpl
value=shuqi.dubbotest.spi.activate.ValueActivateExtImpl
order1=shuqi.dubbotest.spi.activate.OrderActivateExtImpl1
order2=shuqi.dubbotest.spi.activate.OrderActivateExtImpl2
shuqi.dubbotest.spi.activate.ActivateExt1Impl1

上车 just do it!

测试一:@Activate注解中声明group

    @Test
    public void testDefault() {
        ExtensionLoader loader = ExtensionLoader.getExtensionLoader(ActivateExt1.class);
        URL url = URL.valueOf("test://localhost/test");
        //查询组为default_group的ActivateExt1的实现
        List list = loader.getActivateExtension(url, new String[]{}, "default_group");
        System.out.println(list.size()); 
        System.out.println(list.get(0).getClass());
    }
1
class shuqi.dubbotest.spi.activate.ActivateExt1Impl1

测试二:@Activate注解中声明多个group

    @Test
    public void test2() {
         URL url = URL.valueOf("test://localhost/test");
        //查询组为group2的ActivateExt1的实现
        List list = ExtensionLoader.getExtensionLoader(ActivateExt1.class).getActivateExtension(url, new String[]{}, "group2");
        System.out.println(list.size());
        System.out.println(list.get(0).getClass());
    }
1
class shuqi.dubbotest.spi.activate.GroupActivateExtImpl

测试三:@Activate注解中声明了group与value

    @Test
    public void testValue() {
        URL url = URL.valueOf("test://localhost/test");
        //根据   key = value1,group =  value
        //@Activate(value = {"value1"}, group = {"value"})来激活扩展
        url = url.addParameter("value1", "value");
        List list = ExtensionLoader.getExtensionLoader(ActivateExt1.class).getActivateExtension(url, new String[]{}, "value");  
        System.out.println(list.size());
        System.out.println(list.get(0).getClass());
    }
1
class shuqi.dubbotest.spi.activate.ValueActivateExtImpl

测试四:@Activate注解中声明了order,低的排序优先级搞

    @Test
    public void testOrder() {
        URL url = URL.valueOf("test://localhost/test");
        List list = ExtensionLoader.getExtensionLoader(ActivateExt1.class).getActivateExtension(url, new String[]{}, "order");
        System.out.println(list.size());
        System.out.println(list.get(0).getClass());
        System.out.println(list.get(1).getClass());
    }
2
class shuqi.dubbotest.spi.activate.OrderActivateExtImpl2
class shuqi.dubbotest.spi.activate.OrderActivateExtImpl1

结论:

从上面的几个测试用例,可以得到下面的结论:1. 根据loader.getActivateExtension中的group和搜索到此类型的实例进行比较,如果group能匹配到,就是我们选择的,也就是在此条件下需要激活的。2. @Activate中的value是参数是第二层过滤参数(第一层是通过group),在group校验通过的前提下,如果URL中的参数(k)与值(v)中的参数名同@Activate中的value值一致或者包含,那么才会被选中。相当于加入了value后,条件更为苛刻点,需要URL中有此参数并且,参数必须有值。3.@Activate的order参数对于同一个类型的多个扩展来说,order值越小,优先级越高。

源码分析

下面我们带着上面的结论,看一下源码。上篇文章我们我们说了ExtensionLoader.getExtensionLoader(ActivateExt1.class)这一步做的,今天我们从他的下一步讲起getActivateExtension

 /**
     * Get activate extensions.
     *
     * @param url    url
     * @param values extension point names
     * @param group  group
     * @return extension list which are activated
     * @see com.alibaba.dubbo.common.extension.Activate
     */
    public List getActivateExtension(URL url, String[] values, String group) {
        List exts = new ArrayList();
        /**
         * 将传递过来的values包装成List类型的names
         */
        List names = values == null ? new ArrayList(0) : Arrays.asList(values);
        /**
         * 包装好的数据中不包含"-default"
         */
        if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
            /**
             * 获取这个类型的数据的所有扩展信息
             */
            getExtensionClasses();
            for (Map.Entry entry : cachedActivates.entrySet()) {
                /**
                 * 获取扩展的名称
                 */
                String name = entry.getKey();
                /**
                 * 获取扩展的注解
                 */
                Activate activate = entry.getValue();
                /**
                 * 判断group是否属于范围
                 *
                 * 1. 如果activate注解的group没有设定,直接返回true
                 * 2. 如果设定了,需要和传入的额group进行比较,看是否
                 * 包含其中,如果包含,返回true
                 *
                 */
                if (isMatchGroup(group, activate.group())) {
                    /**
                     * group 校验通过了,从缓存中获取此name对应的实例
                     */
                    T ext = getExtension(name);
                    /**
                     * names 不包含 遍历此时的name
                     */
                    if (!names.contains(name)
                            /**
                             * names中不包含"-default"
                             */
                            && !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
                            /**
                             * 通过URL判断这个activate注解是激活的
                             */
                            && isActive(activate, url)) {
                        /**
                         * 增加扩展
                         */
                        exts.add(ext);
                    }
                }
            }
            /**
             * 按照Activate的方式进行排序,注意order
             */
            Collections.sort(exts, ActivateComparator.COMPARATOR);
        }
        /**
         * 借用usrs这个临时变量,进行循环往exts中塞具体的ext的对象。
         * 如果碰到了"default"就添加到头部,清空usrs这个临时变量。
         * 如果没有"default"那么usrs不会清空,所以下面有个if,说usrs不为空
         * 将里面的内容增加到exts中
         */
        List usrs = new ArrayList();
        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)) {
                    if (!usrs.isEmpty()) {
                        exts.addAll(0, usrs);
                        usrs.clear();
                    }
                } else {
                    T ext = getExtension(name);
                    usrs.add(ext);
                }
            }
        }
        if (!usrs.isEmpty()) {
            exts.addAll(usrs);
        }
        return exts;
    }

看到了我们属性的方法getExtensionClasses获取这个类型所有的扩展类,随后利用了cachedActivates变量,不知道大家还有没有印象,他的赋值也是再getExtensionClasses方法里面的

Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
    //存在,就往cachedActivates里面添加名称与注解
    cachedActivates.put(names[0], activate);
}

回顾完毕,我们继续说。首先会校验group,对应的方法是isMatchGroup

    /**
     * 判断group是否属于范围
     * 

* 1. 如果activate注解的group没有设定,直接返回true * 2. 如果设定了,需要和传入的额group进行比较,看是否 * 包含其中,如果包含,返回true */ 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; }

如果activate注解的group没有设定,直接返回true,如果设定了,需要和传入的额group进行比较,看是否包含其中,如果包含,返回true,这个点可以看一下测试方法一和二。group 验证完后,根据name获取具体的扩展。随后验证一下是否激活的方法isActive,主要就是根据@Activate注解中的value和URL中的参数的对应做的。

private boolean isActive(Activate activate, URL url) {
        String[] keys = activate.value();
        /**
         * 如果@Activate注解中的value是空的直接返回true
         */
        if (keys.length == 0) {
            return true;
        }
        /**
         * 从activate.value()拿到的数据进行遍历
         */
        for (String key : keys) {
            /**
             * 从URL中获取参数,进行遍历,如果有一个参数同key一致,或者是以.key的方式结尾。
             * 
             */
            for (Map.Entry 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;
    }

如果@Activate注解中的value是空的直接返回true,如果有值,会遍历判断,从URL中获取参数,进行遍历,如果有一个参数同key一致,或者是以.key的方式结尾,并且url中这个k对应的v有有意义的值,同样返回true,说道这里,我们可以看一下测试三,说的就是这个功能。group和value都验证通过后,就可以添加到exts集合中了,随后进行了排序Collections.sort(exts, ActivateComparator.COMPARATOR) 这个可以看一下测试方法四。当所有的过滤条件都通过后,就可以返回了。

相比于@Adaptive来说,@Activate简单一点,处理逻辑也没那么烦乱。

适用场景

主要用在filter上,有的filter需要在provider边需要加的,有的需要在consumer边需要加的,根据URL中的参数指定,当前的环境是provider还是consumer,运行时决定哪些filter需要被引入执行。

测试源码

dubbo-test测试源码

预告,看这里

下一篇: Dubbo SPI 补充知识点-IOC

END

你可能感兴趣的:(Dubbo SPI之Activate详解)