Dubbo源码分析-SPI的应用

SPI简介

SPI是Service Provider Interface的缩写,即服务提供接口(翻译出来好绕口,还是不翻译的好),实质上是接口,作用是对外提供服务。
SPI是Java的一种插件机制,可以不用修改源代码实现新功能的扩展。
主要有如下几个步骤:

  1. 实现SPI接口
  2. 在项目的META-INF/services文件夹下,新建一个以SPI接口命名的文件, 文件里面配置上SPI接口的实现类
  3. 使用java.util.ServiceLoader加载。
    由于本篇文章主要讲解Dubbo是如何使用SPI的,如果想要具体了解Java的SPI,可以参考下面两篇文章:

Dubbo SPI

回到正题,SPI在dubbo应用的地方很多,专业一点讲叫做微内核机制;
如下图:
Plug-In

我们拿其中一个标签进行讲解,我们在使用dubbo框架时,会配置<dubbo:protocol />标签,告诉dubbo服务的主机、端口、可接收的最大连接数、使用哪个协议,协议的传输控制器(netty,servlet,jetty等)、线程池类型大小等信息。dubbo协议默认使用的是netty网络传输框架,当然还可以使用mina、grizzly,只需要配置transporter、server、client为相应的值即可。那dubbo是如何根据不同的配置使用不同的网络传输框架的呢,当然是通过SPI啦。java spi有一个配置文件,那dubbo是否也有呢?在dubbo-rpc包下的dubbo-rpc-dubbo子包下,发现了一个配置文件
image.png

我们来看下配置文件的内容:

dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol

配置了一个键值对,key为dubbo,值为org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol,在其它几个子包下,也有名称叫做org.apache.dubbo.rpc.Protocol的配置文件,说明Protocol插口有几个对应的插件

可以猜测一下,当<dubbo:protocol />仅仅配置了name=”dubbo”,port=”20880”时,会加载哪一个协议插件呢,根据名称,可以猜测,加载的DubboProtocol插件。那dubbo是怎样做到的呢,我们来一探究竟。

Dubbo为使用SPI做的准备工作:

三个注解

  • SPI:这个注解使用在接口上,标识接口是否是extension(扩展或插口),可以接收一个默认的extension名称
  • Adaptive: 这个注解可以使用在类或方法上,决定加载哪一个extension,值为字符串数组,数组中的字符串是key值,比如new String[]{“key1”,”key2”};先在URL中寻找key1的值,如果找到,则使用此值加载extension,如果key1没有,则寻找key2的值,如果key2也没有,则使用接口SPI注解的值,如果接口SPI注解,没有配置默认值,则将接口名按照首字母大写分成多个部分,然后以’.’分隔,例如org.apache.dubbo.xxx.YyyInvokerWrapper接口名会变成yyy.invoker.wrapper,然后以此名称做为key到URL寻找,如果仍没有找到,则抛出IllegalStateException异常;Adaptive注解用在类上,表示此类是它实现接口(插口)的自适应插件
  • Activate:这个注解可以使用在类或方法上,用以根据URL的key值判断当前extension是否生效,当一个extension有多个实现时,可以加载特定的extension实现类,例如extension实现类上有注解@Activate(“cache, validation”),则当URL上出现”cache”或“validation” key时,当前extension才会生效

ExtensionLoader

顾名思义,ExtensionLoader用于加载extension,它的作用有三点:1.自动加载extension;2.自动包装(wrap) extension;3.创建自适应的(adaptive)extension;

旅途开始

先看下上篇文章中Provider端的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="hello-world-app" />

<!-- 使用multicast广播注册中心暴露服务地址 -->
<dubbo:registry address="multicast://224.5.6.7:1234" />

<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880" />

<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" />

<!-- 和本地bean一样实现服务 -->
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl" />
</beans>

还是先从ClassPathXmlApplicationContext加载spring配置文件说起,上回我们说到ClassPathXmlApplicationContext会使用XmlBeanDefinitionReader将xml文件解析成BeanDefiniton集合,当解析<dubbo:protocol />标签时,会将其解析成org.apache.dubbo.config.ProtocolConfig对象(为什么?请看上回分解最后,protocol key 实例化DubboBeanDefinitionParser时传入的参数),解析<dubbo:service />时,会将其解析成org.apache.dubbo.config.spring.ServiceBean对象。在解析xml时,会调用AbstractApplicationContext的refresh()方法

ServiceBean是ServiceConfig的子类,所以在创建ServiceBean对象的时候,会去先实例化父类,ServiceConfig中有一个static final成员变量protocol

1
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

ExtensionLoader终于出场了,想要获取插件,得分两步走,第一步得到Protocol的插件加载对象extensionLoader,然后由这个加载对象获得对应的插件。
先来看第一步:

1
2
3
4
5
6
7
8
9
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
//一些检查的代码,省略
ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}

EXTENSION_LOADERS保存的是目前已经保存的插口的加载类,显然第一次加载的时候,Protocol还没有自己的插件加载类,那么需要实例化一个。实例化加载对象之后,用这个对象去加载插件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public T getAdaptiveExtension() {
//从已经缓存的自适应对象中获得,第一次调用时还没有创建自适应类,所以instance为null
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
//创建一个自适应类
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
return (T) instance;
}

主要关注 instance = createAdaptiveExtension();这句,createAdaptiveExtension()方法是什么样的呢?

1
2
3
4
5
6
7
8
private T createAdaptiveExtension() {
try {
//得到自适应类并实现化,然后注入属性值
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}

getAdaptiveExtensionClass():

1
2
3
4
5
6
7
8
9
10
private Class<?> getAdaptiveExtensionClass() {
//1.获取所有实现Protocol插口的插件类
getExtensionClasses();
//2.如果有自适应插件类,则返回
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
//3.如果没有,则创建插件类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

先来看上面的第1步,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
42
43
44
45
46
 private Map<String, Class<?>> getExtensionClasses() {
//从缓存中获取插件类,第一次肯定没有
Map<String, Class<?>> classes = cachedClasses.get();
if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get();
if (classes == null) {
//实际的加载插件类方法
classes = loadExtensionClasses();
cachedClasses.set(classes);
}
}
}
return classes;
}

//ExtensionLoader中的三个常量,加载插件的目录,第一个熟悉吧,是java spi的默认目录
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

private Map<String, Class<?>> loadExtensionClasses() {
//获取插口上SPI注解的值,默认值只能有一个,如果多于一个,则抛异常
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
String[] names = NAME_SEPARATOR.split(value);
if (names.length > 1) {
throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
+ ": " + Arrays.toString(names));
}
if (names.length == 1) cachedDefaultName = names[0];
}
}

//加载以上三个目录下的实现了相应插口的插件类(本例中插口是Protocol)
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
return extensionClasses;
}

实现Protocol插口的共有四个插件:
protocol插件

再来看上面getAdaptiveExtensionClass方法的第2步,这一句是判断有没有自适应类,在加载配置的插件过程中,会判断此插件类是不是自适应插件类,判断的依据就是插件类上是否有注解@Adaptive,Protocol的这四个插件类上都没有此注解,所以没有自适应插件,则会走到第3步,创建一个自适应插件类

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
private Class<?> createAdaptiveExtensionClass() {
//生成类代码
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
//得到编辑器,并将类代码编译成字节码
org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
return compiler.compile(code, classLoader);
}

//来看看生成类代码的过程,以生成Protocol插件类代码为例
private String createAdaptiveExtensionClassCode() {
StringBuilder codeBuilder = new StringBuilder();
//得到Protocol接口所有方法
Method[] methods = type.getMethods();
boolean hasAdaptiveAnnotation = false;
for (Method m : methods) {
if (m.isAnnotationPresent(Adaptive.class)) {
hasAdaptiveAnnotation = true;
break;
}
}
// // 如果方法上没有@Adaptive注解,则不能创建自适应插件类
if (!hasAdaptiveAnnotation)
throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");

codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";");
//类名为Protocol$Adaptive实现了Protocol接口
codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {");

for (Method method : methods) {
Class<?> rt = method.getReturnType();
Class<?>[] pts = method.getParameterTypes();
Class<?>[] ets = method.getExceptionTypes();

Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
if (adaptiveAnnotation == null) {
code.append("throw new UnsupportedOperationException(\"method ")
.append(method.toString()).append(" of interface ")
.append(type.getName()).append(" is not adaptive method!\");");
} else {
int urlTypeIndex = -1;
for (int i = 0; i < pts.length; ++i) {
if (pts[i].equals(URL.class)) {
urlTypeIndex = i;
break;
}
}
// 如果发现方法中的参数有一个URL类型
if (urlTypeIndex != -1) {
// Null Point check
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
urlTypeIndex);
code.append(s);

s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);
code.append(s);
}
// 如果没有发现,则会寻找每一个参数类型中的属性是否有为URL类型的
else {
String attribMethod = null;

// find URL getter method
LBL_PTS:
for (int i = 0; i < pts.length; ++i) {
Method[] ms = pts[i].getMethods();
for (Method m : ms) {
String name = m.getName();
if ((name.startsWith("get") || name.length() > 3)
&& Modifier.isPublic(m.getModifiers())
&& !Modifier.isStatic(m.getModifiers())
&& m.getParameterTypes().length == 0
&& m.getReturnType() == URL.class) {
urlTypeIndex = i;
attribMethod = name;
break LBL_PTS;
}
}
}
//如果没找到,则抛出异常
if (attribMethod == null) {
throw new IllegalStateException("fail to create adaptive class for interface " + type.getName()
+ ": not found url parameter or url attribute in parameters of method " + method.getName());
}

// Null point check
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
urlTypeIndex, pts[urlTypeIndex].getName());
code.append(s);
s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
code.append(s);

s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);
code.append(s);
}

String[] value = adaptiveAnnotation.value();
// value is not set, use the value generated from class name as the key
if (value.length == 0) {
char[] charArray = type.getSimpleName().toCharArray();
StringBuilder sb = new StringBuilder(128);
for (int i = 0; i < charArray.length; i++) {
if (Character.isUpperCase(charArray[i])) {
if (i != 0) {
sb.append(".");
}
sb.append(Character.toLowerCase(charArray[i]));
} else {
sb.append(charArray[i]);
}
}
value = new String[]{sb.toString()};
}

boolean hasInvocation = false;
for (int i = 0; i < pts.length; ++i) {
if (pts[i].getName().equals("org.apache.dubbo.rpc.Invocation")) {
// Null Point check
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
code.append(s);
s = String.format("\nString methodName = arg%d.getMethodName();", i);
code.append(s);
hasInvocation = true;
break;
}
}
String defaultExtName = cachedDefaultName;
String getNameCode = null;
for (int i = value.length - 1; i >= 0; --i) {
if (i == value.length - 1) {
if (null != defaultExtName) {
if (!"protocol".equals(value[i]))
if (hasInvocation)
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
} else {
if (!"protocol".equals(value[i]))
if (hasInvocation)
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
else
getNameCode = "url.getProtocol()";
}
} else {
if (!"protocol".equals(value[i]))
//如果方法参数类型名称为"org.apache.dubbo.rpc.Invocation"则从url获取以此参数类型名为key的值,获取不到则取默认扩展名,即Protocol接口上注解SPI的值“dubbo”
if (hasInvocation)
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
//否则,取从url中取以方法上注解adaptive的值为key对应的值
getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
else
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
code.append("\nString extName = ").append(getNameCode).append(";");
// check extName == null?
String s = String.format("\nif(extName == null) " +
"throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");",
type.getName(), Arrays.toString(value));
code.append(s);

s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
code.append(s);

// return statement
if (!rt.equals(void.class)) {
code.append("\nreturn ");
}

s = String.format("extension.%s(", method.getName());
code.append(s);
for (int i = 0; i < pts.length; i++) {
if (i != 0)
code.append(", ");
code.append("arg").append(i);
}
code.append(");");
}

codeBuilder.append("\npublic ").append(rt.getCanonicalName()).append(" ").append(method.getName()).append("(");
for (int i = 0; i < pts.length; i++) {
if (i > 0) {
codeBuilder.append(", ");
}
codeBuilder.append(pts[i].getCanonicalName());
codeBuilder.append(" ");
codeBuilder.append("arg").append(i);
}
codeBuilder.append(")");
if (ets.length > 0) {
codeBuilder.append(" throws ");
for (int i = 0; i < ets.length; i++) {
if (i > 0) {
codeBuilder.append(", ");
}
codeBuilder.append(ets[i].getCanonicalName());
}
}
codeBuilder.append(" {");
codeBuilder.append(code.toString());
codeBuilder.append("\n}");
}
codeBuilder.append("\n}");
if (logger.isDebugEnabled()) {
logger.debug(codeBuilder.toString());
}
return codeBuilder.toString();
}

我们来看下生成的插件类Protocol$Adaptive代码:

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
package org.apache.dubbo.rpc;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {

public void destroy()
{throw new UnsupportedOperationException("method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}
public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
}

public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null");
org.apache.dubbo.common.URL url = arg1;
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
}
public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");org.apache.dubbo.common.URL url = arg0.getUrl();
String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
if(extName == null) throw new IllegalStateException("Fail to get extension(org.apache.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.export(arg0);
}
}

可以看出此类可以根据url中参数protocol值加载对应的插件,如果url中没有,则加载名为”dubbo”对应的插件,而从前面加载的四个插件可以看出,名称为dubbo的插件类为org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol.

写到这里总算将SPI加载的过程大体上讲述了一篇,Dubbo中还有许多类似的插件,原理基本相同;除了有的插口有自适应插件,比如org.apache.dubbo.common.compiler.Compilerorg.apache.dubbo.common.extension.ExtensionFactory,自适应插件类上都有注解@Adaptive,比如Compile的自适应插件AdaptiveCompiler,ExtensionFactory的自适应插件AdaptiveExtensionFactory.

为什么要提供自适应插件,而不是都在运行时生成?

答:
(1)解决鸡生蛋,蛋生鸡的问题,上面createAdaptiveExtensionClass方法中,在第1步生成Protocol$Adaptive类后,会使用编译器将其编译成字节码,但是编译器本身也是插件化的,可以有好几种编译器,所以需要提供一个已经存在的自适应编译器(AdaptiveCompiler),然后在编译的时候,使用此编译器找到Compile接口上SPI注解中配置的默认的编译器进行编译。
(2)解决对象生成方式不同导致的加载问题;Dubbo中对象的生成一类是由Spring容器创建,一类是根据插件文件的配置动态加载;所以要想获取这两部分对象,需要使用不同的方式;而AdaptiveExtensionFactory就是为了解决这个问题,在获取对象时,分别从Spring容器和ExtensionLoader中查找。