前言
前方还是借用官网的解释,我觉得说的还是蛮好的。
Dubbo 中的很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,然而有些拓展不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。Dubbo通过代理模式就可以解决这个问题,这里将具有代理功能的拓展称之为自适应拓展。Dubbo 并未直接通过代理模式实现自适应拓展,而是代理代理模式基础上,封装了另一种实现方式。Dubbo 首先会为拓展接口生成具有代理功能的代码,然后通过 javassist 或 jdk 编译这段代码,得到 Class 类,最后在通过反射创建代理类。
获取自适应拓展getAdaptiveExtension
让我们从Dubbo获取自适应扩展的方法开始,这个方法在上篇的SPI机制的文章中,在ExtensionLoader的构造方法中出现过。
当时是为了获取ExtensionFactory自适应扩展对象。那么我们就从这个点开始。
private ExtensionLoader(Class<?> type) {
this.type = type;
objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}
点进去如下
public T getAdaptiveExtension() {
// 还是从缓存中获取先。
Object instance = cachedAdaptiveInstance.get();
if (instance == null) {
if (createAdaptiveInstanceError != null) {
throw new IllegalStateException("Failed to create adaptive instance: " +
createAdaptiveInstanceError.toString(),
createAdaptiveInstanceError);
}
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get();
if (instance == null) {
try {
// 还是没有就创建。
instance = createAdaptiveExtension();
cachedAdaptiveInstance.set(instance);
} catch (Throwable t) {
createAdaptiveInstanceError = t;
throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
}
}
}
}
return (T) instance;
}
先从缓存中获取,没有就去创建。那么下面看看createAdaptiveExtension方法
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance());
} catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}
代码不多,就几行,但调用了三个方法。
- 使用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象
- 通过反射进行实例化
- 使用用 injectExtension 方法向拓展实例中注入依赖
有关injectExtension在上篇中已经分析过了,就不再赘述了,有兴趣的可以取上篇看看。
那么从第一个getAdaptiveExtensionClass方法来看。
private Class<?> getAdaptiveExtensionClass() {
// 获取所有的扩展类。
getExtensionClasses();
// 如果自适应缓存不为空,那么直接返回。
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 创建自适应拓展类。
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
有关getExtensionClasses我在上篇已经解释过了。至于这个cachedAdaptiveClass变量,在上篇的loadClass方法中,如果类上有Adaptive注解,那么这个值就是该class。就拿ExtensionFactory举例,在加载它所有的扩展类后,只有AdaptiveExtensionFactory有该Adaptive注解,那么,该方法就会直接返回AdaptiveExtensionFactory而不进行下面的createAdaptiveExtensionClass了。当然,如果接口下的实现类都没有Adaptive注解,就还是走。下面看下createAdaptiveExtensionClass。
private Class<?> createAdaptiveExtensionClass() {
// 构建自适应拓展代码。
String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
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);
}
具体怎么编译的就不仔细去看了,这里主要看的是如何生成自适应扩展类的源码。其中AdaptiveClassCodeGenerator的cachedDefaultName是SPI注解的value,可能是null。下面来看generate
public String generate() {
// 如果没有自适应方法,那么不需要生成自适应类,就报错。
if (!hasAdaptiveMethod()) {
throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
}
// 使用StringBuilder去衔接。
StringBuilder code = new StringBuilder();
code.append(generatePackageInfo());
code.append(generateImports());
code.append(generateClassDeclaration());
// 遍历class下的所有方法。
Method[] methods = type.getMethods();
for (Method method : methods) {
code.append(generateMethod(method));
}
code.append("}");
if (logger.isDebugEnabled()) {
logger.debug(code.toString());
}
return code.toString();
}
由于里面方法众多,也不需要延伸,那么我分批来。
#自适应扩展类代码生成
// 生成 package 代码,如:package org.apache.dubbo.common.extension;
private String generatePackageInfo() {
return String.format(CODE_PACKAGE, type.getPackage().getName());
}
// 生成 import 代码,如:import + com.aaa.bbb.Ccc
private String generateImports() {
return String.format(CODE_IMPORTS, ExtensionLoader.class.getName());
}
// 生成类代码:public class Adaptive { 这样的
private String generateClassDeclaration() {
return String.format(CODE_CLASS_DECLARATION, type.getSimpleName(), type.getCanonicalName());
}
首先是生成自适应类的一些前缀信息。所属包名,引用的类,已经类名啥啥的。
下面看下如何生成里面的具体方法
private String generateMethod(Method method) {
// 获取方法返回类型
String methodReturnType = method.getReturnType().getCanonicalName();
// 获取方法名
String methodName = method.getName();
// 获取内容。
String methodContent = generateMethodContent(method);
// 获取方法参数
String methodArgs = generateMethodArguments(method);
// 获取异常
String methodThrows = generateMethodThrows(method);
return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}
获取方法返回类型和获取方法名都比较好理解。主要是看如果获取方法的内容和方法参数,获取异常的那个就先不管。
生成方法内容
private String generateMethodContent(Method method) {
// 获取方法上的Adaptive注解
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
// 如果没有,那么返回的是异常信息:"The method %s of interface %s is not adaptive method!");
if (adaptiveAnnotation == null) {
return generateUnsupported(method);
} else {
// 获取URL类型的参数索引,拿Protocol来举例,它的refer方法的第二个参数是URL,那么这个值就是1。
// 没有URL类型的就返回 -1.
int urlTypeIndex = getUrlTypeIndex(method);
// 针对有URL和没URL是两种处理。
if (urlTypeIndex != -1) {
// 有URL参数我们就是这样:
// if (arg%d == null) throw new IllegalArgumentException("url == null"); URL url = arg%d;
// 其中的 %d 是urlTypeIndex。说白了就是多一层URL为空的校验
code.append(generateUrlNullCheck(urlTypeIndex));
} else {
// <1>
code.append(generateUrlAssignmentIndirectly(method));
}
// 获取方法上Adaptive注解的值。
String[] value = getMethodAdaptiveValue(adaptiveAnnotation);
// 获取方法的参数类型是否有为Invocation类型的。
boolean hasInvocation = hasInvocationArgument(method);
// 生成Invocation参数的判空代码片段。
code.append(generateInvocationArgumentNullCheck(method));
// <2> 生成为extName分配代码。
code.append(generateExtNameAssignment(value, hasInvocation));
// 生成变量extName空检查的代码。
code.append(generateExtNameNullCheck(value));
// 生成 %s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);
// 第三个是ExtensionLoader类名,其他都是%s都是type name。
code.append(generateExtensionAssignment());
// 生成目标方法调用逻辑,格式为:return extension.%s(%s);
// 第一个%s,是方法名,第二个是参数,就是调用要调用的方法。
code.append(generateReturnAndInvocation(method));
}
return code.toString();
}
上面一个个来。 先从没有URL参数的生成逻辑来,
<1>generateUrlAssignmentIndirectly
// 我们就假设传进来的是Protocol下没有URL参数的export方法
private String generateUrlAssignmentIndirectly(Method method) {
// 获取参数类型。
Class<?>[] pts = method.getParameterTypes();
Map<String, Integer> getterReturnUrl = new HashMap<>();
// 遍历这些参数的类型下的方法。
for (int i = 0; i < pts.length; ++i) {
for (Method m : pts[i].getMethods()) {
String name = m.getName();
// 如果有 方法名以“get”开头 或者 name长度 > 3
// 同时需要是public、非静态的、方法没参数、返回值是URL的。
// 那么就将该方法名和i的映射放进map中。
if ((name.startsWith("get") || name.length() > 3)
&& Modifier.isPublic(m.getModifiers())
&& !Modifier.isStatic(m.getModifiers())
&& m.getParameterTypes().length == 0
&& m.getReturnType() == URL.class) {
getterReturnUrl.put(name, i);
}
}
}
// 如果找不到,那么抛异常。
// 说白了,就是export方法参数列表中即使没有URL参数,那么它参数中也要有能返回URL的参数类型。
// export方法中的唯一一个参数Invoker<T> invoker就是能返回URL的
if (getterReturnUrl.size() <= 0) {
// getter method not found, throw
throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName()
+ ": not found url parameter or url attribute in parameters of method " + method.getName());
}
// 获取名称为“getUrl”的位置。
Integer index = getterReturnUrl.get("getUrl");
if (index != null) {
return generateGetUrlNullCheck(index, pts[index], "getUrl");
} else {
Map.Entry<String, Integer> entry = getterReturnUrl.entrySet().iterator().next();
return generateGetUrlNullCheck(entry.getValue(), pts[entry.getValue()], entry.getKey());
}
}
generateGetUrlNullCheck方法主要是:
- 生成检查arg [i]是否为空
- 生成检查arg [i].getXX()是否为空。
- 生成一个赋值URL 方法 = arg [i].getXX()
还是拿export方法举例。
它会生成如下:
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();
<3>generateExtNameAssignment
这个方法看上去有点复杂,但蛮好理解的。
// value是Adaptive注解的值,hasInvocation表示方法的所有参数中是否有为Invocation类型的
private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
String getNameCode = null;
// 从Adaptive注解的值列表最后一个来
for (int i = value.length - 1; i >= 0; --i) {
// 如果是最后一个
if (i == value.length - 1) {
// 同时defaultExtName不为空
// 该defaultExtName是new AdaptiveClassCodeGenerator时传进来的cachedDefaultName
// 上面也解释过了,是SPI 注解的value,可能为null
if (null != defaultExtName) {
// 如果值不是protocol
if (!"protocol".equals(value[i])) {
// 如果有方法中有参数为Invocation类型
if (hasInvocation) {
// 分支1
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
// 分支2
getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
}
} else {
// 如果值是protocol
// 分支3
getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName);
}
} else {
// 如果defaultExtName为空
// 值不是protocol
if (!"protocol".equals(value[i])) {
// 有有参数为Invocation类型
if (hasInvocation) {
// 分支4
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
// 分支5
getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
}
} else {
// 值是protocol
// 分支6
getNameCode = "url.getProtocol()";
}
}
} else {
// 如果值不是protocol
if (!"protocol".equals(value[i])) {
if (hasInvocation) {
// 分支7
getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
} else {
// 分支8
getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
}
} else {
// 分支9
getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
}
}
}
// String extName = %s; %s就是各个分支下来的。
return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
}
上面其实就是按不同的条件进入不同的生成代码片段中,难倒是不难,就是看着挺烦。下面我们拿两个来举例吧,首先拿负载均衡的LoadBalance来举例。
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;
}
首先能看见SPI注解有个RandomLoadBalance.NAME,就是“random”,同时有Invocation类型的参数,那么hasInvocation就为true,value[]这个数组就是只有loadbalance一个值,defaultExtName不为空,而是为random。
那么综合来看:
value = [“loadbalance”];
hasInvocation = true;
defaultExtName = "random"
于是会走分支1,生成如下代码:
String extName = url.getMethodParameter(“select”, "loadbalance", "random");
这个方法比较简单,那再看一个。
@SPI("netty")
public interface Transporter {
@Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY})
RemotingServer bind(URL url, ChannelHandler handler) throws RemotingException;
@Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY})
Client connect(URL url, ChannelHandler handler) throws RemotingException;
}
只分析一个bind哈,毕竟都差不多,按上面的分析,各变量值为:
value = [“server”, "transporter"];
hasInvocation = false;
defaultExtName = "netty"
于是会走两遍for循环。第一遍会走分支2生成如下:
url.getParameter("transporter", "netty");
第二遍走分支8,连带第一遍的生成如下:
String extName = url.getParameter(“server”, url.getParameter("transporter", "netty"));
都举了两个例子了,应该不难理解吧?自觉写的还是比较通俗易懂的,再不理解可以多看两遍,不难的。
总结
好了,这个自适应代码还是不难的,不过我觉得也没必要看的太细,明白有这么一个方法就行。就像Spring,咱们大体明白就行,各个都了解,那看不完的。