上面我们聊到了ExtensionLoader.getAdaptiveExtension()的实现源码,这篇咱们来聊一聊Dubbo SPI最后一种——ExtensionLoader.getActiveExtension(),也就是激活扩展点的实现。那么在本篇安排的内容如下:
- 还是回忆
- 继续深入
- 持续填坑
- 最后总结
回忆
上篇我们说到了ExtensionLoader.getAdaptiveExtension()
,那么我们这篇聊聊ExtensionLoader.getActivateExtension()
,这种方式获取的扩展点我们称为激活扩展点,顾名思义,激活指的是需要特定的条件才激活才生效。那么我们回忆下它的规则吧。
-
首先接口少不了,在接口上加上
@SPI
注解,在指定位置定义一个以接口全限定名为名的文件,在里面定义key和实现类的全限定类名。 -
在实现类上加上
@Activate
注解,该注解有如下属性:value
:激活条件,只有在URL中有该条件作为参数,就激活该扩展。group
:通过group过滤,需要配合ExtensionLoader#getActivateExtension(URL, String, String)使用。只有当传递过来的group在配置的group中,才会激活。before
:配置相对位置。也就是可以通过这个来配置实现类的顺序after
:同上,也是和顺序相关order
:配置绝对位置,同上,也是和顺序相关
-
万事具备,只差使用了。使用方式如下(从前面的文章中Copy出来的):
1
2
3
4
5
6
7
8
9
10
11
12
13public class App {
public static void main( String[] args ){
ExtensionLoader<IHelloService> extensionLoader = ExtensionLoader.getExtensionLoader(IHelloService.class);
URL url = new URL("http","localhost",8080);
//key为@Activate的value属性,而value就是配置文件中你需要获取的实现类对应的key.多个可以用“,”分割
url = url.addParameter("impl","helloServiceImpl,helloServiceProvider");
//这里的第二个参数传你需要进行激活的key。也就是说,在url中要有该key,才会创建类的实例
List<IHelloService> impl = extensionLoader.getActivateExtension(url, "impl");
for (IHelloService iHelloService : impl) {
System.out.println(iHelloService.sayHello(url));
}
}
} -
回忆完毕,下面进入正题——分析它的实现
深入
入口点:getActivateExtension(url,string)
-
先来一起看看他的源码
1
2
3
4public List<T> getActivateExtension(URL url, String key) {
//调用重载方法
return getActivateExtension(url, key, null);
} -
继续深入
getActivateExtension(url, key, null)
,源码如下:1
2
3
4
5
6
7
8public List<T> getActivateExtension(URL url, String key, String group) {
//从url中获取key对应的value
String value = url.getParameter(key);
//调用重载方法,传递从url中取出来的value.如果value配置有多个,则以","分割。
//这也是我们回忆部分,parameter中的value用","分割多个的原因
//COMMA_SPLIT_PATTERN是一个正则,\s*[,]+\s*
return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
} -
getActivateExtension(URL url, String[] values, String group)
源码如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52public List<T> getActivateExtension(URL url, String[] values, String group) {
List<T> exts = new ArrayList<>();
List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values);
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses();
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
String name = entry.getKey();
Object activate = entry.getValue();
String[] activateGroup, activateValue;
if (activate instanceof Activate) {
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
if (isMatchGroup(group, activateGroup)) {
T ext = getExtension(name);
if (!names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) {
exts.add(ext);
}
}
}
exts.sort(ActivateComparator.COMPARATOR);
}
List<T> usrs = new ArrayList<>();
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
if (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;
}- 上面就是
getActivateExtension
的具体实现了。下面我们一段一段解答它。
- 上面就是
getActivateExtension(URL url,String[] values,String group)
解读
起始
-
我们先看如下代码
1
2
3
4
5
6
7
8//存放最终的结果
List<T> exts = new ArrayList<>();
List<String> names = values == null ? new ArrayList<>(0) : Arrays.asList(values);
if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
getExtensionClasses();
// .....以下代码省略
}
// .....以下代码省略if
前面和判断我们先忽略。映入眼帘的就是getExtensionClasses();
,这段代码已经是老相识了,里面的内容我也就不说了,总之,这里是会加载以对应接口全限定名为名的文件中配置的实现类。那么这里面还有一个坑未填。我们下面把他堵上。
填坑
-
还记得下面的代码吗?位于
ExtesionLoader.loadClass()
方法中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//判断配置文件中的类
if (clazz.isAnnotationPresent(Adaptive.class)) {
//缓存类级别自适应扩展点
//.....
} else if (isWrapperClass(clazz)) {
//如果是Warpper类,则缓存起来
//.....
} else {
//....
String[] names = NAME_SEPARATOR.split(name);
if (ArrayUtils.isNotEmpty(names)) {
//缓存激活扩展点
cacheActivateClass(clazz, names[0]);
for (String n : names) {
//...
//保存实现类到map
saveInExtensionClass(extensionClasses, clazz, name);
}
}
} -
想起来了么?上面
if...else if...
的坑我们都已经填完了。接下来就开始填最后一个else
中缓存激活扩展点的坑。他的代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//clazz为实现类,name为配置的key
private void cacheActivateClass(Class<?> clazz, String name) {
//获取类上面的@Activate注解。如果没有就是返回null
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
//不为空,就往map中put。注意这里不是put实现类,而是注解
cachedActivates.put(name, activate);
} else {
//如果为空,则尝试获取一下之前alibaba包下面的Activate注解。
//因为没有捐给apache之前,dubbo的包名是alibaba。捐给apache之后,包名都是apache之类的。
//这里这么做是为了兼容Dubbo没有捐给apache之前的版本,不然人家升上来还得改包引用
com.alibaba.dubbo.common.extension.Activate oldActivate = clazz.getAnnotation(com.alibaba.dubbo.common.extension.Activate.class);
if (oldActivate != null) {
//同样,不为空就put到map。为空就什么都不做
cachedActivates.put(name, oldActivate);
}
}
}- 从上面代码看,先判断实现类上是否有加注解,有加注解就把注解信息put到Map。那么我们的实现类是保存到哪了呢?它会在外层调用
saveInExtensionClass()
方法,保存到extensionClasses
中。那么到这里,最后一个坑也算是填完了。
- 从上面代码看,先判断实现类上是否有加注解,有加注解就把注解信息put到Map。那么我们的实现类是保存到哪了呢?它会在外层调用
继续
-
我们回到
getActivateExtension()
方法,上面仅仅是在getExtensionClasses()
方法中做的操作。下面我们继续看这段代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
//这里我们刚刚看过了。
getExtensionClasses();
//从这个for开始看。看看在循环中做了什么
for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
//entry.getKey() ==> 获取的是我们在文件中配置的key
String name = entry.getKey();
//entry.getValue() ==> 注解类
Object activate = entry.getValue();
String[] activateGroup, activateValue;
//判断注解是apache包下面的还是dubbo包下面的。从而进行不同的操作
if (activate instanceof Activate) {
//获取配置的group信息和激活条件
activateGroup = ((Activate) activate).group();
activateValue = ((Activate) activate).value();
} else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
//这里也是一样,获取配置的group信息和激活条件。只是包不同而已。
activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
} else {
continue;
}
//匹配group
if (isMatchGroup(group, activateGroup)) {
//如果作为参数传入的group和我们加在@Activate注解上面的group匹配
//就通过name获取到我们对应的实现类
T ext = getExtension(name);
//names是在url中通过key得到的value
//name是在配置文件中配置的key
//处理仅用参数名做匹配,而参数值无意义的情况。
if (!names.contains(name)
&& !names.contains(REMOVE_VALUE_PREFIX + name)
&& isActive(activateValue, url)) {
//把扩展类示例放到结果集中。
exts.add(ext);
}
}
}
//上面一系列操作做完后,在这里排序,这个排序和在注解上配置的位置有关。
exts.sort(ActivateComparator.COMPARATOR);
} -
我们看看他group是怎么匹配的。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//group是在我们main方法中传递过来的。groups是我们在注解上面配置的
private boolean isMatchGroup(String group, String[] groups) {
//如果我们作为参数传过来的group是空串,那么直接返回true
if (StringUtils.isEmpty(group)) {
return true;
}
//如果注解中获取的groups不为空,然后main方法中传递的group也不为空和空串。
//循环比对,匹配上就是true。如果全部都没匹配上,就返回false
if (groups != null && groups.length > 0) {
for (String g : groups) {
if (group.equals(g)) {
return true;
}
}
}
return false;
}- 匹配规则也很简单,如下:
- 如果我们从main方法穿过来的group为null或者空串(该参数在调用时是可选参数,所以可能是null或空串),直接返回true,匹配成功。
- 如果group不为null或空串,并且groups也不为空,就一个一个比对,只要有一个匹配上了就返回true,如果都不匹配就返回false。
- 匹配规则也很简单,如下:
-
再然后看一下
isActive()
方法,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25//keys是在实现类上的注解的value属性,也就是激活条件
//url就是我们在main方法的传参。
private boolean isActive(String[] keys, URL url) {
//如果条件为空,就直接返回true。
//那就说明我这个实现类没有任何的激活条件,可以直接使用。
if (keys.length == 0) {
return true;
}
//遍历条件
for (String key : keys)
//获取url中的所有参数遍历
for (Map.Entry<String, String> entry : url.getParameters().entrySet()) {
// k-->参数的key
String k = entry.getKey();
// v-->参数对应的值
String v = entry.getValue();
//如果url中有在注解中配置的value值作为参数,并且参数值不为空,就返回true
if ((k.equals(key) || k.endsWith("." + key))
&& ConfigUtils.isNotEmpty(v)) {
return true;
}
}
}
return false;
}- 判断是否激活的依据总结如下:
- 如果在注解上没有配置条件,那自然而然就返回true。也就是说,这个实现类可以不需要条件进行使用。
- 上面说了注解上没有设置条件的情况处理。那设置了条件会怎样?
- 遍历注解上配置的条件,并且遍历我们url中的参数
- 如果url中有条件作为参数名,并且参数值不为空,则说明匹配成功,就返回true,代表该实现类已经激活。
- 还有一种情况就是如果参数中是
.条件
作为参数结尾的,并且参数不为空,这时候也匹配成功,返回true。
- 其他情况都是false。
- 判断是否激活的依据总结如下:
-
在继续往下,看最后一段代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24List<T> usrs = new ArrayList<>();
//names为我们通过传入的参数key在url中得到得到的值
for (int i = 0; i < names.size(); i++) {
String name = names.get(i);
//REMOVE_VALUE_PREFIX ==》 ‘-’
if (!name.startsWith(REMOVE_VALUE_PREFIX)
&& !names.contains(REMOVE_VALUE_PREFIX + name)) {
if (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;- 这里我的猜测是处理参数值为指定的扩展实现类的情况。也就是回忆部分demo的那种情况。
-
那我们总结下这段代码。
-
初始化扩展实现类到缓存(
getExtensionClasses
) -
遍历该接口在实现类上配置的
@Activate
注解集合。-
读取在注解上配置的group和value属性
-
使用main方法中传过来group和注解上配置的group进行比对。
- 如果传过来的group为空,则默认认为是匹配的。
- 如果匹配不成功,这返回false
-
匹配成功后,调用
getExtension
获取对应的实现类 -
如果是仅仅用参数名作为匹配,而参数值无意义的情况,就会通过url中的参数名和注解上配置的条件做比对。
- 如果注解上没有条件,那默认返回true
- 如果注解上有条件,就循环和url中的参数名做比对,如果相同并且参数值不为空,就返回true
- 其他情况为false。
-
加入到结果集。
-
-
遍历URL中根据参数名获取的参数值。处理参数值为我们的SPI实现类的情况。
-
总结
以上这些(包括前几篇文章)就是Dubbo中SPI中的源码分析了。我们来一次大的总结吧,为后面分析dubbo的服务注册源码做铺垫。
- 最基本的一种扩展点获取方式
ExtensionLoader.getExtension(string)
,它的实现如下:- 先从缓存中根据name获取值。如果获取不到,就走加载的逻辑。
- 读取指定目录下面的以接口全限定类名为名的文件,解析里面配置的key和对应的实现类
- 根据实现类的特点,以及接口的特点进行分类。
- 缓存在实现类上加了
@Adaptive
的实现类。这就是我们自己写的代理。 - 如果是
Wrapper
增强的类,也缓存起来 - 缓存激活扩展点,这点在上面已经说了。
- 剩下的就是普通的实现类了,也缓存起来
- 缓存在实现类上加了
- 依赖注入(set方法)
- 然后就是我们的自适应扩展点了,也就是
ExstionLoder.getAdaptiveExtension()
。调用这个方法得到的是一个代理类。具体的实现类是需要通过这个代理类去返回的。方法实现如下:- 先从缓存中获取,如果取不到就往下面走。取到了就返回
- 如果是有类级别的自适应扩展实现类,就直接返回。也就是说,如果
@Adaptive
加在类实现类上,就把这个实现类返回。即我们手动写的代理类。(这种方式在dubbo中少见) - 如果没有,就会根据接口,自动生成我们的代理类。
- 创建实例,然后使用set方法进行依赖注入。
- 再然后就是今天说的激活扩展点的实现了。
- 首先会初始化对应的扩展类。
- 如果有配置group,就会先匹配group,匹配到了在通过条件(注解的value值)去匹配。
- 最终返回匹配的实现类。
ok。上面就是SPI的全部内容了。根据个人理解,肯定会有点偏差。希望大佬们能够不吝指教。谢谢~~