上篇我们简单讲了dubbo Adaptive的使用,没有太多代码,一个接口和几个实现类,还有一个配置文件和测试类。接口和实现类没有什么讲的,因此入口就从测试类开始。
测试代码如下:
// 1、获取 extExtensionLoader
ExtensionLoader<AdaptiveExt> extExtensionLoader = ExtensionLoader.getExtensionLoader(AdaptiveExt.class);
// 2、获取自适应扩展点
AdaptiveExt adaptiveExt = extExtensionLoader.getAdaptiveExtension();
// 3、URL参数
URL url = URL.valueOf("test://localhost/test?adaptive.ext=spring");
// 4、调用
System.out.println(adaptiveExt.echo("d", url));
-
ExtensionLoader.getExtensionLoader
已经讲解过,现在我们直接看getAdaptiveExtension
方法,代码如下:
/**
* 获取自适应扩展的instance
*/
@SuppressWarnings("unchecked")
public T getAdaptiveExtension() {
// 从缓存中获取自适应扩展instance
Object instance = cachedAdaptiveInstance.get();
// 没有命中缓存
if (instance == null) {
// 创建自适应扩展instance异常为空
if (createAdaptiveInstanceError == null) {
// 加锁,双重检查
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
// 继续没有命中缓存
if (instance == null) {
try {
// 创建自适应扩展instance
instance = createAdaptiveExtension();
// set
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
} else {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
}
}
// 返回自适应扩展instance
return (T) instance;
}
getAdaptiveExtension
方法首先会检查缓存,缓存未命中,则调用 createAdaptiveExtension
方法创建自适应拓展。createAdaptiveExtension
代码如下:
/**
* 创建自适应扩展instance
*/
@SuppressWarnings("unchecked")
private T createAdaptiveExtension() {
try {
// 获取自适应拓展类,并通过反射实例化,然后就是dubbo IOC 调用 injectExtension 方法向拓展实例中注入依赖
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
createAdaptiveExtension
方法的代码比较少,但却包含了三个逻辑,分别如下:
- 调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象
- 通过反射进行实例化
- 调用 injectExtension 方法向拓展实例中注入依赖
前两个逻辑比较好理解,第三个逻辑用于向自适应拓展对象中注入依赖。这里调用 injectExtension 方法的目的是为手工编码的自适应拓展注入依赖,这一点需要大家注意一下。关于 injectExtension 方法,前文已经分析过了,这里不再赘述。接下来,分析 getAdaptiveExtensionClass
方法的逻辑。
/**
* 获取自适应扩展的类
*/
private Class<?> getAdaptiveExtensionClass() {
// 通过 SPI 获取所有的拓展类,在这一步判断所有的所有的扩展类上有没有 Adaptive 注解,如果有,将 cachedAdaptiveClass 赋值
getExtensionClasses();
// 判断 cachedAdaptiveClass 是否有值,如果有直接返回
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 创建自适应拓展类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
getAdaptiveExtensionClass
方法同样包含了三个逻辑,如下:
- 调用
getExtensionClasses
获取所有的拓展类 - 检查缓存,若缓存不为空,则返回缓存
- 若缓存为空,则调用
createAdaptiveExtensionClass
创建自适应拓展类
首先从第一个逻辑说起,getExtensionClasses 这个方法用于获取某个接口的所有实现类。比如该方法可以获取 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等实现类。在获取实现类的过程中,如果某个某个实现类被 Adaptive 注解修饰了,那么该类就会被赋值给 cachedAdaptiveClass 变量。如果所有的实现类均未被 Adaptive 注解修饰,那么执行第三步逻辑,创建自适应拓展类。相关代码如下:
/**
* 创建自适应拓展类
*/
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();
// 编译代码,生成 Class
return compiler.compile(code, classLoader);
}
createAdaptiveExtensionClass
方法用于生成自适应拓展类,该方法首先会生成自适应拓展类的源码,然后通过Compiler
实例(Dubbo 默认使用 javassist
作为编译器)编译源码,得到代理类 Class 实例。
- 接下来,我们把重点放在代理类代码生成的逻辑上,即
createAdaptiveExtensionClassCode
方法上。
- 在生成代理类源码之前,createAdaptiveExtensionClassCode 方法首先会通过反射检测接口方法是否包含 Adaptive 注解。对于要生成自适应拓展的接口,Dubbo 要求该接口至少有一个方法被 Adaptive 注解修饰。若不满足此条件,就会抛出运行时异常。相关代码如下:
// type是扩展点class
Method[] methods = type.getMethods();
boolean hasAdaptiveAnnotation = false;
for (Method m : methods) {
// 检测方法上是否有 Adaptive 注解
if (m.isAnnotationPresent(Adaptive.class)) {
hasAdaptiveAnnotation = true;
break;
}
}
// no need to generate adaptive class since there's no adaptive method found.
if (!hasAdaptiveAnnotation) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
- 通过 Adaptive 注解检测后,即可开始生成代码。代码生成的顺序与 Java 文件内容顺序一致,首先会生成 package 语句,然后生成 import 语句,紧接着生成类名等代码。整个逻辑如下:
// 生成 package 代码:package + type 所在包
codeBuilder.append("package ").append(type.getPackage().getName()).append(";");
// 生成 import 代码:import + ExtensionLoader 全限定名
codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";");
// 生成类代码:public class + type简单名称 + $Adaptive + implements + type全限定名 + {
codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {");
这里以上篇的AdaptiveExt
为例,生成的代码如下:
package com.hui.wang.dubbo.learn.dubbo.adaptive;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class AdaptiveExt$Adaptive implements com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt {
}
- 接着开始生成对应的方法,一个方法可以被
Adaptive
注解修饰,也可以不被修饰。这里将未被Adaptive
注解修饰的方法称为“无Adaptive
注解方法”。Dubbo 不会为没有标注Adaptive
注解的方法生成代理逻辑,对于该种类型的方法,仅会生成一句抛出异常的代码。生成逻辑如下:
for (Method method : methods) {
// 省略无关逻辑
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
// 如果方法上无 Adaptive 注解,则生成 throw new UnsupportedOperationException(...) 代码
if (adaptiveAnnotation == null) {
// 生成的代码格式如下:
// throw new UnsupportedOperationException(
// "method " + 方法签名 + of interface + 全限定接口名 + is not adaptive method!”)
code.append("throw new UnsupportedOperationException(\"method ")
.append(method.toString()).append(" of interface ")
.append(type.getName()).append(" is not adaptive method!\");");
} else {
// 省略无关逻辑
}
// 省略无关逻辑
}
以 dubbo 中的 com.alibaba.dubbo.rpc.Protocol
接口的destroy
方法为例,上面代码生成的内容如下:
public void destroy() {
throw new UnsupportedOperationException(
"method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
前面说过方法代理逻辑会从 URL 中提取目标拓展的名称,因此代码生成逻辑的一个重要的任务是从方法的参数列表或者其他参数中获取 URL 数据。举例说明一下,我们要为 com.alibaba.dubbo.rpc.Protocol
接口的refer
和 export
方法生成代理逻辑。refer
和 export
方法方法签名如下:
<T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
<T> Invoker<T> refer(Class<T> aClass, URL url) throws RpcException;
对于 refer 方法,通过遍历 refer 的参数列表即可获取 URL 数据,这个还比较简单。对于 export 方法,获取 URL 数据则要麻烦一些。export 参数列表中没有 URL 参数,因此需要从 Invoker 参数中获取 URL 数据。获取方式是调用 Invoker 中可返回 URL 的 getter 方法,比如 getUrl。如果 Invoker 中无相关 getter 方法,此时则会抛出异常。整个逻辑如下:
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 {
int urlTypeIndex = -1;
// 遍历参数列表,确定 URL 参数位置
for (int i = 0; i < pts.length; ++i) {
if (pts[i].equals(URL.class)) {
urlTypeIndex = i;
break;
}
}
// urlTypeIndex != -1,表示参数列表中存在 URL 参数
if (urlTypeIndex != -1) {
// 为 URL 类型参数生成判空代码,格式如下:
// if (arg + urlTypeIndex == null)
// throw new IllegalArgumentException("url == null");
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");",
urlTypeIndex);
code.append(s);
// 为 URL 类型参数生成赋值代码,形如 URL url = arg1
s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex);
code.append(s);
// 参数列表中不存在 URL 类型参数
} else {
String attribMethod = null;
LBL_PTS:
// 遍历方法的参数类型列表
for (int i = 0; i < pts.length; ++i) {
// 获取某一类型参数的全部方法
Method[] ms = pts[i].getMethods();
// 遍历方法列表,寻找可返回 URL 的 getter 方法
for (Method m : ms) {
String name = m.getName();
// 1. 方法名以 get 开头,或方法名大于3个字符
// 2. 方法的访问权限为 public
// 3. 非静态方法
// 4. 方法参数数量为0
// 5. 方法返回值类型为 URL
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;
// 结束 for (int i = 0; i < pts.length; ++i) 循环
break LBL_PTS;
}
}
}
if (attribMethod == null) {
// 如果所有参数中均不包含可返回 URL 的 getter 方法,则抛出异常
throw new IllegalStateException("fail to create adaptive class for interface ...");
}
// 为可返回 URL 的参数生成判空代码,格式如下:
// if (arg + urlTypeIndex == null)
// throw new IllegalArgumentException("参数全限定名 + argument == null");
String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");",
urlTypeIndex, pts[urlTypeIndex].getName());
code.append(s);
// 为 getter 方法返回的 URL 生成判空代码,格式如下:
// if (argN.getter方法名() == null)
// throw new IllegalArgumentException(参数全限定名 + argument getUrl() == null);
s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");",
urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod);
code.append(s);
// 生成赋值语句,格式如下:
// URL全限定名 url = argN.getter方法名(),比如
// com.alibaba.dubbo.common.URL url = invoker.getUrl();
s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod);
code.append(s);
}
// 省略无关代码
}
// 省略无关代码
}
这段代码主要目的是为了获取 URL 数据,并为之生成判空和赋值代码。以Protocol
的 refer 和 export 方法为例,上面的代码为它们生成如下内容(代码已格式化):
refer
:
if (arg1 == null) {
throw new IllegalArgumentException("url == null");
}
com.alibaba.dubbo.common.URL url = arg1;
export
:
if (arg0 == null) {
throw new IllegalArgumentException(
"com.alibaba.dubbo.rpc.Invoker argument == null");
}
if (arg0.getUrl() == null) {
throw new IllegalArgumentException(
"com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
}
com.alibaba.dubbo.common.URL url = arg0.getUrl();
- 接下来获取
Adaptive
注解值,Adaptive 注解值 value 类型为 String[],可填写多个值,默认情况下为空数组。若 value 为非空数组,直接获取数组内容即可。若 value 为空数组,则需进行额外处理。处理过程是将类名转换为字符数组,然后遍历字符数组,并将字符放入 StringBuilder 中。若字符为大写字母,则向 StringBuilder 中添加点号,随后将字符变为小写存入 StringBuilder 中。比如 LoadBalance 经过处理后,得到 load.balance。
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 数据}
String[] value = adaptiveAnnotation.value();
// value 为空数组
if (value.length == 0) {
// 获取类名,并将类名转换为字符数组
String splitName = StringUtils.camelToSplitName(type.getSimpleName(), ".");
value = new String[]{splitName};
}
// 省略无关代码
}
// 省略无关逻辑
}
- 检测 Invocation 参数,此段逻辑是检测方法列表中是否存在 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;
}
}
}
// 省略无关逻辑
}
生成的代码示例为:
if (argN == null) {
throw new IllegalArgumentException("invocation == null");
}
String methodName = argN.getMethodName();
- 生成拓展名获取逻辑,下面代码是根据 SPI 和 Adaptive 注解值生成“获取拓展名逻辑”,同时生成逻辑也受 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 注解值}
// ${检测 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);
}
// 省略无关逻辑
}
上面代码比较复杂,不是很好理解。对于这段代码,建议大家写点测试用例,对 Protocol、LoadBalance 以及 Transporter 等接口的自适应拓展类代码生成过程进行调试。这里我以 Transporter 接口的自适应拓展类代码生成过程举例说明。首先看一下 Transporter 接口的定义,如下:
@SPI("netty")
public interface Transporter {
// @Adaptive({server, transporter})
@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
Server bind(URL url, ChannelHandler handler) throws RemotingException;
// @Adaptive({client, transporter})
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
下面对 connect 方法代理逻辑生成的过程进行分析,此时生成代理逻辑所用到的变量如下:
String defaultExtName = "netty";
boolean hasInvocation = false;
String getNameCode = null;
String[] value = ["client", "transporter"];
根据上面的代码逻辑,对 value 数组进行遍历,此时 i = 1, value[i] = "transporter",生成的代码如下:
getNameCode = url.getParameter("transporter", "netty");
接下来,for 循环继续执行,此时 i = 0, value[i] = "client",生成的代码如下:
getNameCode = url.getParameter("client", url.getParameter("transporter", "netty"));
for 循环结束运行,现在为 extName 变量生成赋值和判空代码,如下:
String extName = url.getParameter("client", url.getParameter("transporter", "netty"));
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString()
+ ") use keys([client, transporter])");
}
- 生成拓展加载与目标方法调用逻辑,下面代码逻辑用于根据拓展名加载拓展实例,并调用拓展实例的目标方法。
// 生成拓展获取代码,格式如下:
// type全限定名 extension = (type全限定名)ExtensionLoader全限定名
// .getExtensionLoader(type全限定名.class).getExtension(extName);
// Tips: 格式化字符串中的 %<s 表示使用前一个转换符所描述的参数,即 type 全限定名
s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);",
type.getName(), ExtensionLoader.class.getSimpleName(), type.getName());
code.append(s);
// 如果方法返回值类型非 void,则生成 return 语句。
if (!rt.equals(void.class)) {
code.append("\nreturn ");
}
// 生成目标方法调用逻辑,格式为:
// extension.方法名(arg0, arg2, ..., argN);
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(");");
}
以 Protocol 接口举例说明,上面代码生成的内容如下:
com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader
.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
return extension.refer(arg0, arg1);
- 生成完整的方法,下面代码生成的收尾工作,主要用于生成方法定义的代码。相关逻辑如下:
// public + 返回值全限定名 + 方法名 + (
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}");
以 Protocol 的 refer 方法为例,上面代码生成的内容如下:
public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) {
// 方法体
}
- 关于自适应拓展的原理,实现就分析完了。总的来说自适应拓展整个逻辑还是很复杂的,并不是很容易弄懂。因此,大家在阅读该部分源码时,耐心一些。同时多进行调试,也可以通过生成好的代码思考代码的生成逻辑。
下面贴出上篇中的AdaptiveExt
接口生成的自适应类的代码:
package com.hui.wang.dubbo.learn.dubbo.adaptive;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class AdaptiveExt$Adaptive implements com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt {
public java.lang.String echo(java.lang.String arg0,
com.alibaba.dubbo.common.URL arg1) {
if (arg1 == null) {
throw new IllegalArgumentException("url == null");
}
com.alibaba.dubbo.common.URL url = arg1;
String extName = url.getParameter("myAdaptiveName");
if (extName == null) {
throw new IllegalStateException(
"Fail to get extension(com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt) name from url(" +
url.toString() + ") use keys([myAdaptiveName])");
}
com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt extension = (com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt) ExtensionLoader.getExtensionLoader(com.hui.wang.dubbo.learn.dubbo.adaptive.AdaptiveExt.class)
return extension.echo(arg0, arg1);
}
}
看到这个生成的代码是不是和上篇写的AdaptiveExtProxy
大致差不多。
参考示例
- 编写一个测试样例,代码如下,用于生成
com.alibaba.dubbo.remoting.Transporter
和com.alibaba.dubbo.rpc.Protocol
自适应类。
@Test
public void testAdaptive() throws Exception{
ExtensionLoader<Transporter> transporterExtensionLoader = ExtensionLoader.getExtensionLoader(Transporter.class);
transporterExtensionLoader.getAdaptiveExtension();
ExtensionLoader<Protocol> extensionLoader = ExtensionLoader.getExtensionLoader(Protocol.class);
extensionLoader.getAdaptiveExtension();
}
- 这里给大家推荐一个阿里的开源工具,Arthas,直接将运行的类反编译成Java文件。
下面我贴出com.alibaba.dubbo.remoting.Transporter
和com.alibaba.dubbo.rpc.Protocol
自适应类的代码:
public class Protocol$Adaptive implements Protocol {
@Override
public void destroy() {
throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
@Override
public int getDefaultPort() {
throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
}
public Invoker refer(Class class_, URL uRL) throws RpcException {
String string;
if (uRL == null) {
throw new IllegalArgumentException("url == null");
}
URL uRL2 = uRL;
String string2 = string = uRL2.getProtocol() == null ? "dubbo" : uRL2.getProtocol();
if (string == null) {
throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(").append(uRL2.toString()).append(") use keys([protocol])").toString());
}
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
return protocol.refer(class_, uRL);
}
public Exporter export(Invoker invoker) throws RpcException {
String string;
if (invoker == null) {
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
}
if (invoker.getUrl() == null) {
throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
}
URL uRL = invoker.getUrl();
String string2 = string = uRL.getProtocol() == null ? "dubbo" : uRL.getProtocol();
if (string == null) {
throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(").append(uRL.toString()).append(") use keys([protocol])").toString());
}
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(string);
return protocol.export(invoker);
}
}
public class Transporter$Adaptive implements Transporter {
@Override
public Client connect(URL uRL, ChannelHandler channelHandler) throws RemotingException {
if (uRL == null) {
throw new IllegalArgumentException("url == null");
}
URL uRL2 = uRL;
String string = uRL2.getParameter("client", uRL2.getParameter("transporter", "netty"));
if (string == null) {
throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(").append(uRL2.toString()).append(") use keys([client, transporter])").toString());
}
Transporter transporter = ExtensionLoader.getExtensionLoader(Transporter.class).getExtension(string);
return transporter.connect(uRL, channelHandler);
}
@Override
public Server bind(URL uRL, ChannelHandler channelHandler) throws RemotingException {
if (uRL == null) {
throw new IllegalArgumentException("url == null");
}
URL uRL2 = uRL;
String string = uRL2.getParameter("server", uRL2.getParameter("transporter", "netty"));
if (string == null) {
throw new IllegalStateException(new StringBuffer().append("Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(").append(uRL2.toString()).append(") use keys([server, transporter])").toString());
}
Transporter transporter = ExtensionLoader.getExtensionLoader(Transporter.class).getExtension(string);
return transporter.bind(uRL, channelHandler);
}
}