dubbo SPI
dubbo自适应扩展机制
自适应拓展类加载过程
- 加载入口方法
- 加载@SPI接口所有的实现类,如果有实现类有@Adaptive注解,则直接返回此类。
- 判断@SPI接口中的方法是否至少有一个方法具有@Adaptive注解,如果没有,则抛异常。
- 通过反射获取接口的方法数组,然后遍历数组生成代理类内部代码。
- 对于没有@Adaptive注解的方法,生成的代理方法内部直接抛出异常。
- 代理方法体核心逻辑就是要获取代理类的在配置文件中对应的名称,即extName,然后加载对应的实现类。
- 获取URL参数
以Protocol为例
- 获取 Adaptive 注解值,注解值是一个String数组 values,如果注解没有值则把SPI接口名的转成小写字母放入values。根据这些值从URL中获取的值就是extName。
-
检测 Invocation 参数
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) {
// ${无 Adaptive 注解方法代码生成逻辑}
} else {
// ${获取 URL 数据}
// ${获取 Adaptive 注解值}
boolean hasInvocation = false;
// 遍历参数类型列表
for (int i = 0; i < pts.length; ++i) {
// 判断当前参数名称是否等于 com.alibaba.dubbo.rpc.Invocation
if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) {
// 为 Invocation 类型参数生成判空代码
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i);
code.append(s);
// 生成 getMethodName 方法调用代码,格式为:
// String methodName = argN.getMethodName();
s = String.format("\nString methodName = arg%d.getMethodName();", i);
code.append(s);
// 设置 hasInvocation 为 true
hasInvocation = true;
break;
}
}
}
// 省略无关逻辑
}
- extName获取逻辑,从后至前遍历values,从URL中获取对应的值,最终values数组中第一个值对应URL中的值就是最后的extName。
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) {
// $无 Adaptive 注解方法代码生成逻辑}
} else {
// ${获取 URL 数据}
// ${获取 Adaptive 注解值}
// ${检测 Invocation 参数}
// 设置默认拓展名,cachedDefaultName 源于 SPI 注解值,默认情况下,
// SPI 注解值为空串,此时 cachedDefaultName = null
String defaultExtName = cachedDefaultName;
String getNameCode = null;
// 遍历 value,这里的 value 是 Adaptive 的注解值,2.2.3.3 节分析过 value 变量的获取过程。
// 此处循环目的是生成从 URL 中获取拓展名的代码,生成的代码会赋值给 getNameCode 变量。注意这
// 个循环的遍历顺序是由后向前遍历的。
for (int i = value.length - 1; i >= 0; --i) {
// 当 i 为最后一个元素的坐标时
if (i == value.length - 1) {
// 默认拓展名非空
if (null != defaultExtName) {
// protocol 是 url 的一部分,可通过 getProtocol 方法获取,其他的则是从
// URL 参数中获取。因为获取方式不同,所以这里要判断 value[i] 是否为 protocol
if (!"protocol".equals(value[i]))
// hasInvocation 用于标识方法参数列表中是否有 Invocation 类型参数
if (hasInvocation)
// 生成的代码功能等价于下面的代码:
// url.getMethodParameter(methodName, value[i], defaultExtName)
// 以 LoadBalance 接口的 select 方法为例,最终生成的代码如下:
// url.getMethodParameter(methodName, "loadbalance", "random")
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
// 生成的代码功能等价于下面的代码:
// url.getParameter(value[i], defaultExtName)
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
else
// 生成的代码功能等价于下面的代码:
// ( url.getProtocol() == null ? defaultExtName : url.getProtocol() )
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
// 生成的代码功能等价于下面的代码:
// url.getParameter(value[i])
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
else
// 生成从 url 中获取协议的代码,比如 "dubbo"
getNameCode = "url.getProtocol()";
}
} else {
if (!"protocol".equals(value[i]))
if (hasInvocation)
// 生成代码格式同上
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
else
// 生成的代码功能等价于下面的代码:
// url.getParameter(value[i], getNameCode)
// 以 Transporter 接口的 connect 方法为例,最终生成的代码如下:
// url.getParameter("client", url.getParameter("transporter", "netty"))
getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
else
// 生成的代码功能等价于下面的代码:
// url.getProtocol() == null ? getNameCode : url.getProtocol()
// 以 Protocol 接口的 connect 方法为例,最终生成的代码如下:
// url.getProtocol() == null ? "dubbo" : url.getProtocol()
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
// 生成 extName 赋值代码
code.append("\nString extName = ").append(getNameCode).append(";");
// 生成 extName 判空代码
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);
}
// 省略无关逻辑
}
- 使用extName加载拓展