前期准备
一. 增加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