Dubbo SPI @Activate注解分析
Dubbo @Activate
注解机制是对Dubbo SPI
机制的扩展,该注解用在SPI
接口实现的定义上,表明这些SPI
扩展接口的实现类被激活或者不被激活的条件,比如Filter
接口有很多实现,如AccessLogFilter/GenericImplFilter
等,Dubbo
框架在RPC
调用过程中可指定具体的条件,与这些SPI
接口实现类@Activate
注解上要求的条件进行匹配,成功则这些SPI
实现类激活,否则不被激活,从而在RPC
调用中起到作用。
@Activate注解
@Activate
注解可以注解在类和方法上,该注解定义如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
/**
* Group过滤条件。
* <br />
* 包含{@link ExtensionLoader#getActivateExtension}的group参数给的值,则返回扩展。
* <br />
* 如没有Group设置,则不过滤。
*/
String[] group() default {};
/**
* Key过滤条件。包含{@link ExtensionLoader#getActivateExtension}的URL的参数Key中有,则返回扩展。
* <p/>
* 示例:<br/>
* 注解的值 <code>@Activate("cache,validatioin")</code>,
* 则{@link ExtensionLoader#getActivateExtension}的URL的参数有<code>cache</code>Key,或是<code>validatioin</code>则返回扩展。
* <br/>
* 如没有设置,则不过滤。
*/
String[] value() default {};
/**
* 排序信息,可以不提供。值为扩展点的名字name
*/
String[] before() default {};
/**
* 排序信息,可以不提供。值为扩展点的名字name
*/
String[] after() default {};
/**
* 排序信息,可以不提供。 值为顺序值int
*/
int order() default 0;
}
该注解包含两个匹配条件value
和group
,两者都是字符串数组,用来指定SPI
实现类激活的条件;before/after/order
表示排序顺序,在这些SPI
实现类获取并进行排序时起作用。以com.alibaba.dubbo.rpc.Filter
举例:
// AccessLogFilter,指定如果Filter使用方属于Constants.PROVIDER(provider),并且URL中包含有效的
// Constants.ACCESS_LOG_KEY(accesslog)参数,就激活这个AccessLogFilter过滤器,并且指定了排序顺序
// -8000.
@Activate(group = Constants.PROVIDER, value = Constants.ACCESS_LOG_KEY, order = -8000)
public class AccessLogFilter implements Filter {
// ...
}
// GenericImplFilter,指定如果Filter使用方属于Constants.CONSUMER(consumer),并且URL中包含有效的
// Constants.GENERIC_KEY(generic)参数,就激活这个GenericImplFilter过滤器,并且指定了排序顺序
// 20000.
@Activate(group = Constants.CONSUMER, value = Constants.GENERIC_KEY, order = 20000)
public class GenericImplFilter implements Filter {
}
Dubbo SPI中激活扩展的实现解析
上述表明了@Activate
注解可以配置SPI
实现类被激活使用的条件,下面分析Dubbo SPI
如何指定条件,找到匹配的SPI
扩展实现,实现过程在ExtensionLoader
中。
// 如下的4个方法可用于获取激活的扩展点,前三个方法最终调用的是第4个方法
getActivateExtension(URL url, String key)
getActivateExtension(URL url, String[] values)
getActivateExtension(URL url, String key, String group)
getActivateExtension(URL url, String[] values, String group)
/**
* 根据 url和url参数key 返回激活的扩展点列表
* @param url url
* @param key url参数key,用于获取扩展定实现类的名字
* @return 激活的扩展点
* @see #getActivateExtension(com.alibaba.dubbo.common.URL, String, String)
*/
public List<T> getActivateExtension(URL url, String key) {
return getActivateExtension(url, key, null);
}
/**
* 根据 url和指定的扩展定实现类的名字数组 返回激活的扩展点列表
* @param url url
* @param 扩展定实现类的名字
* @return 激活的扩展点
* @see #getActivateExtension(com.alibaba.dubbo.common.URL, String[], String)
*/
public List<T> getActivateExtension(URL url, String[] values) {
return getActivateExtension(url, values, null);
}
/**
* 根据 url和url参数key以及group 返回激活的扩展点列表
* @param url url
* @param key url参数key,用于获取扩展定实现类的名字
* @param group group
* @return 激活的扩展点
* @see #getActivateExtension(com.alibaba.dubbo.common.URL, String[], String)
*/
public List<T> getActivateExtension(URL url, String key, String group) {
// 根据key,从url获取扩展点实现类的名字数组
String value = url.getParameter(key);
return getActivateExtension(url, value == null || value.length() == 0 ? null : Constants.COMMA_SPLIT_PATTERN.split(value), group);
}
/**
* 获取激活的扩展(最终的激活匹配逻辑在这里)
* @param url url
* @param values 扩展点实现类的名字,用户指定的扩展点实例类
* @param group 用户指定的组
* @return 激活的扩展点
* @see com.alibaba.dubbo.common.extension.Activate
*/
public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<T>(); // 最终返回的激活的扩展点列表
List<String> names = values == null ? new ArrayList<String>(0) : Arrays.asList(values); // 根据参数key从url获取到的扩展点实现类的名字
// 名字中不包含“-default”
if (!names.contains(Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY)) {
// 加载所有的扩展点实现类
getExtensionClasses();
// 在getExtensionClasses()调用过程中,遇到注解了@Activate注解的SPI实现类,会把
// 实现类的name和@Activate实例以Key-Value的形式存入cachedActivates,便于下面匹配
// 激活的扩展时使用
/* loadFile(Map<String, Class<?>> extensionClasses, String dir)中
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
*/
for (Map.Entry<String, Activate> entry : cachedActivates.entrySet()) {
String name = entry.getKey(); // 扩展点实现类的名字
Activate activate = entry.getValue(); // 扩展点实现类上@Activate实例
if (isMatchGroup(group, activate.group())) { // 先根据group匹配
// group匹配后获取扩展点实例
T ext = getExtension(name);
// 1. 根据参数key从url获取到的扩展点实现类的名字中不包含当前遍历的实现类名字
// 2. 不排除当前遍历的实现类名字(-name)
// 3. activate实例的value 在url有对应参数key和有效的值
if (!names.contains(name)
&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name)
&& isActive(activate, url)) {
exts.add(ext);
}
}
}
// 根据Activate注解的before、after、order信息,对匹配的扩展点实现类实例排序
Collections.sort(exts, ActivateComparator.COMPARATOR);
}
List<T> usrs = new ArrayList<T>(); // usrs暂存扩展点实现类
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
// name不是以-开始,或者names不包含-name(没有排除name)
if (!name.startsWith(Constants.REMOVE_VALUE_PREFIX)
&& !names.contains(Constants.REMOVE_VALUE_PREFIX + name)) {
if (Constants.DEFAULT_KEY.equals(name)) {
// 遇到default,就把用户指定的扩展点实现类中default之前的实现类放到exts前面
if (usrs.size() > 0) {
exts.addAll(0, usrs);
usrs.clear();
}
} else {
// 否则先暂存到usrs
T ext = getExtension(name);
usrs.add(ext);
}
}
}
// 把用户指定的扩展点实现类中default之后的实现类放到exts后面
if (usrs.size() > 0) {
exts.addAll(usrs);
}
return exts; // 返回符合条件的激活扩展
}
/**
* group是用户指定的组,groups是扩展点实现类@Activate注解上设置的组
*/
private boolean isMatchGroup(String group, String[] groups) {
// 如果用户获取激活的扩展点时未指定group,直接返回true,表示该条件匹配
if (group == null || group.length() == 0) {
return true;
}
// group不为空时,需要看是否group在groups的范围内,是则返回true;否则返回false
if (groups != null && groups.length > 0) {
for (String g : groups) {
if (group.equals(g)) {
return true;
}
}
}
return false;
}
/**
* activate是扩展点实现类对应的@Activate实例
*/
private boolean isActive(Activate activate, URL url) {
String[] keys = activate.value(); // activate配置的value,也就是对应于url上的参数key
// 扩展点实现类对url上的参数key无要求,返回true
if (keys == null || keys.length == 0) {
return true;
}
// 有要求
for (String key : keys) {
// 从url中获取参数,进行遍历,如果有一个参数同key一致,或者是以.key的方式结尾,
// 并且对应的值是有效的,则返回true
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;
}
// 带有@Activate注解的扩展点实现类的比较器
public class ActivateComparator implements Comparator<Object> {
public static final Comparator<Object> COMPARATOR = new ActivateComparator();
@Override
public int compare(Object o1, Object o2) {
if (o1 == null && o2 == null) {
return 0;
}
if (o1 == null) {
return -1;
}
if (o2 == null) {
return 1;
}
if (o1.equals(o2)) {
return 0;
}
Activate a1 = o1.getClass().getAnnotation(Activate.class);
Activate a2 = o2.getClass().getAnnotation(Activate.class);
// 如果配置了before和after信息,则根据这些信息进行比较
if ((a1.before().length > 0 || a1.after().length > 0
|| a2.before().length > 0 || a2.after().length > 0)
&& o1.getClass().getInterfaces().length > 0
&& o1.getClass().getInterfaces()[0].isAnnotationPresent(SPI.class)) {
ExtensionLoader<?> extensionLoader = ExtensionLoader.getExtensionLoader(o1.getClass().getInterfaces()[0]);
if (a1.before().length > 0 || a1.after().length > 0) {
String n2 = extensionLoader.getExtensionName(o2.getClass());
for (String before : a1.before()) {
if (before.equals(n2)) {
return -1;
}
}
for (String after : a1.after()) {
if (after.equals(n2)) {
return 1;
}
}
}
if (a2.before().length > 0 || a2.after().length > 0) {
String n1 = extensionLoader.getExtensionName(o1.getClass());
for (String before : a2.before()) {
if (before.equals(n1)) {
return 1;
}
}
for (String after : a2.after()) {
if (after.equals(n1)) {
return -1;
}
}
}
}
// 否则根据order信息进行比较
int n1 = a1 == null ? 0 : a1.order();
int n2 = a2 == null ? 0 : a2.order();
return n1 > n2 ? 1 : -1; // 就算n1 == n2也不能返回0,否则在HashSet等集合中,会被认为是同一值而覆盖
}
}
根据上面的源码分析,可以得到如下的结论:
- 根据
ExtensionLoader.getActivateExtension
中的group
和搜索到该SPI接口类型的扩展点实现类进行比较,如果group
能匹配到,进行下一步value
的匹配。 @Activate
中的value
是第二层过滤参数(第一层是通过group
),在group
校验通过的前提下,如果url中的参数(k
)与值(v
)中的参数名同@Activate
中的value
值一致或者包含,那么才会被选中。相当于加入了value
后,条件更为苛刻点,需要url
中有此参数并且参数必须存在有效值。@Activate
的order
参数对于同一个类型的多个扩展来说,order
值越小,优先级越高。
应用场景
主要用在Filter
上,有的Filter
需要在provider
边执行,有的需要在consumer
边执行,根据url
中的参数指定和group
(provider
还是consumer
),运行时决定哪些filter
需要被引入执行。
TestCase
实现一个Filter
/**
* LogFilter 自定义Filter,测试Dubbo SPI @Activate机制
*/
@Activate(value = {"log"}, group = {Constants.CONSUMER}, order = -11000)
public class LogFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
System.out.println(invoker.getUrl());
return null;
}
}
// resources目录下新建META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter文件,内容为
log=com.wacai.midldleware.dubbospi.activate.LogFilter
测试
1.
/**
* ExtensionLoader.getActivateExtension()测试1
*/
public class FilterTest {
public static void main(String[] args) {
URL url = URL.valueOf("test://localhost/test?generic=true");
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class)
.getActivateExtension(url, new String[]{}, Constants.CONSUMER);
System.out.println("size: " + filters.size()
+ "---" + Arrays.toString(filters.toArray(new Filter[0])));
}
}
// 输出:
// size: 4---[com.alibaba.dubbo.rpc.filter.ConsumerContextFilter@589838eb, com.wacai.dubbo.rpc.filter.NinjaTraceFilter@42dafa95, com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter@6500df86, com.alibaba.dubbo.rpc.filter.GenericImplFilter@402a079c]
2.
/**
* ExtensionLoader.getActivateExtension()测试2
*/
public class FilterTest {
public static void main(String[] args) {
URL url = URL.valueOf("test://localhost/test?generic=true&log=true");
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class)
.getActivateExtension(url, new String[]{}, Constants.CONSUMER);
System.out.println("size: " + filters.size()
+ "---" + Arrays.toString(filters.toArray(new Filter[0])));
}
}
// 输出:
// size: 5---[com.wacai.midldleware.dubbospi.activate.LogFilter@589838eb, com.alibaba.dubbo.rpc.filter.ConsumerContextFilter@42dafa95, com.wacai.dubbo.rpc.filter.NinjaTraceFilter@6500df86, com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter@402a079c, com.alibaba.dubbo.rpc.filter.GenericImplFilter@59ec2012]
3.
/**
* ExtensionLoader.getActivateExtension()测试3
*/
public class FilterTest {
public static void main(String[] args) {
URL url = URL.valueOf("test://localhost/test?generic=true&log=true");
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class)
.getActivateExtension(url, new String[]{Constants.REMOVE_VALUE_PREFIX + Constants.DEFAULT_KEY, "log"}, Constants.CONSUMER);
System.out.println("size: " + filters.size()
+ "---" + Arrays.toString(filters.toArray(new Filter[0])));
}
}
// 输出:
// size: 1---[com.wacai.midldleware.dubbospi.activate.LogFilter@2280cdac]
- 其他测试用例和
demo
见dubbo
源码测试用例。