一、概述

  本篇介绍自适应扩展,方法getAdaptiveExtension()的实现。ExtensionLoader类本身很多功能也使用到了自适应扩展。包括ExtensionFactory扩展。

  通俗的讲,自适应扩展要实现的逻辑是:调用扩展点的方法时,自动判断要调用那个扩展点实现类的方法。我们知道,一个扩展点通常有多个实现类,在配置文本文件中分多行配置,在前面的分析中,我们知道通过getExtension(String name)方法,返回的是指定key的扩展点,而自适应扩展点方法getAdaptiveExtension()在调用前,不确认返回那个扩展点。而是在方法调用的时候,根据方法入参,进行确定,具体是调用那个实现类。

  自适应扩展基于@Adaptive注解,可以修饰类,也可以修饰方法。修饰类的时候,逻辑比较简单,不会动态生成代码逻辑,使用的场景也比较少,主要包括AdaptiveCompiler 和 AdaptiveExtensionFactory。修饰方法的时候,会动态生成一个新类,新类包括扩展点的所有方法,调用getAdaptiveExtension()返回的就是新类对象。

二、详细介绍

  前面我们说过,@Adaptive可以修饰类,也可以修饰方法。我们先看下修饰类的场景。

  通过一个具体的实现类来看下,这里我们分析AdaptiveCompiler类的实现:

 @Adaptive
public class AdaptiveCompiler implements Compiler { private static volatile String DEFAULT_COMPILER; public static void setDefaultCompiler(String compiler) {
DEFAULT_COMPILER = compiler;
} @Override
public Class<?> compile(String code, ClassLoader classLoader) {
Compiler compiler;
ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
String name = DEFAULT_COMPILER; // copy reference
if (name != null && name.length() > 0) {
compiler = loader.getExtension(name);
} else {
compiler = loader.getDefaultExtension();
}
return compiler.compile(code, classLoader);
} }

  此类,只有一个对外提供的方法compile(String code, ClassLoader classLoader),我们来看方法的实现。

首先沟通ExtensionLoader.getExtensionLoader(Compiler.class),获取Compiler对应的ExtensionLoader对象,然后判断构造器中初始化的DEFAULT_COMPILER 变量是否有值。如果存在就通过loader.getExtension(name)方法获得扩展点实现。如果DEFAULT_COMPILER 为空,则调用loader.getDefaultExtension()方法,返回默认实现。获取compiler扩展点实现对象后,调用对应的compile方法。

  由此,我们可以看到,@Adaptive修饰的类,在调用具体方法的时候,是根据一定的条件进行判断,确认具体调用的实现类对象。

  我们再说下@Adaptive修饰方法的场景。

  扩展点实现类的方法如果被@Adaptive修饰,在调用getAdaptiveExtension()方法时候,程序会自动生成一个新类,新类是一个名为扩展点接口名+$Adaptive,实现了扩展点接口的类。新类中的方法,主要分为两类,一是有@Adaptive注解的方法,一个是没有@Adaptive注解的方法。

  有@Adaptive注解的方法,方法内部会判断方法入参是否有URL(此处是dubbo内的URL),或是方法入参对象是否可以get到URL。如果都不能获取到URL,直接throw 出Exception。如果能获取到URL,则从URL对象中获取需要调用的实现类对应的配置文本文件的key,根据什么参数从URL中获取呢?首先是从@Adaptive的value获取(此value是一个字符串数组),如果@Adaptive为空,则根据类名进行转换,得出从URL获取key的参数名,转换规则是根据驼峰规则,遇到大写字符添加”.“,如 AdaptiveFactory 为:adaptive.factory。获得参数后,再通过getExtension(..)方法,获得需要调用的扩展点实现类对象。

  到这里,我们基本介绍了自适应扩展点的实现逻辑,但是有一点没有说到,就是不管@Adaptive修饰类还是修饰方法,自适应扩展点的返回逻辑,这点是要结合代码进行说明,接下来就开启我们的源代码分析。

三、源代码分析

  我们从getAdaptiveExtension()方法开始

  

 public T getAdaptiveExtension() {
// 从缓存中获取自定义拓展
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;
}

  这个方法的逻辑很简单,主要包括

  1、从缓存对象cachedAdaptiveInstance获取自适应扩展点实例
  2、缓存有直接返回,没有进行方法createAdaptiveExtension()调用
  3、根据方法返回的实例,设置到到缓存里,并进行返回

  所以我们接着分析createAdaptiveExtension方法

 private T createAdaptiveExtension() {
try {
// injectExtension 为@Adaptive注解的类 可能存在的IOC服务
// @Adaptive注解方法 自动生成的代理类不存在IOC可能
T instance = (T) getAdaptiveExtensionClass().newInstance();
return injectExtension(instance);
} catch (Exception e) {
throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
}
}

  可以看到,方法内部是通过getAdaptiveExtensionClass() 获取到Class实例,再反射实例化,获取到实例对象。

  我们接着往下看getAdaptiveExtensionClass()方法

 private Class<?> getAdaptiveExtensionClass() {
// 通过SPI获取所有的扩展类,赋值相关的成员变量
getExtensionClasses();
// 如果有@Adaptive修饰的类,cachedAdaptiveClass不为空
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 没有@Adaptive修饰的类时,根据@Adaptive修饰方法 创建自适应扩展类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

  首先执行的是getExtensionClasses()方法,之后判断cachedAdaptiveClass  是否为空,不为空就直接返回了。这个cachedAdaptiveClass 变量,其实就是有@Adaptive修饰的扩展点实现。也就是说,如果在扩展点的实现类中,存在@Adaptive修饰的类,就直接返回这个类了。

  那么cachedAdaptiveClass 在是哪里赋值的呢?我们需要再看getExtensionClasses()方法。getExtensionClasses这个方法在前面两篇文章中已经都有介绍。在默认扩展点的实现里面,cachedDefaultName变量的赋值就是在这个方法里进行的。cachedAdaptiveClass 的赋值的方法调用链我们这里直接给出来

  

 getExtensionClasses()-->loadExtensionClasses()-->loadDirectory()-->loadResource()-->loadClass()

  隐藏的比较深,第5个方法才对cachedDefaultName进行了赋值。

  我们一步一步来分析,先看getExtensionClasses()

  

 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;
}

  这个方法很简单,我们接着看loadExtensionClasses()

 private Map<String, Class<?>> loadExtensionClasses() {
// 获取注解 SPI的接口
// type为传入的扩展接口,必须有@SPI注解
final SPI defaultAnnotation = type.getAnnotation(SPI.class);
// 获取默认扩展实现value,如果存在,赋值给cachedDefaultName
if (defaultAnnotation != null) {
String value = defaultAnnotation.value();
if ((value = value.trim()).length() > 0) {
// @SPI value 只能是一个,不能为逗号分割的多个
// @SPI value为默认的扩展实现
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];
}
}
// 加载三个目录配置的扩展类
Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
// META-INF/dubbo/internal
loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
// META-INF/dubbo
loadDirectory(extensionClasses, DUBBO_DIRECTORY);
// META-INF/services/
loadDirectory(extensionClasses, SERVICES_DIRECTORY);
return extensionClasses;
}

  很熟悉吧,这个方法我们在第一篇文章中已经有介绍了,我们再接着往下看loadDirectory方法

 private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) {
// 扩展配置文件完整文件路径+文件名
String fileName = dir + type.getName();
try { Enumeration<java.net.URL> urls;
// 获取类加载器
ClassLoader classLoader = findClassLoader();
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
java.net.URL resourceURL = urls.nextElement();
// 加载
loadResource(extensionClasses, classLoader, resourceURL);
}
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t);
}
}

  这个方法我们也很分析过了,再往下看loadResource()方法

 private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
try {
String line;
while ((line = reader.readLine()) != null) {
// 字符#是注释开始标志,只取#前面的字符
final int ci = line.indexOf('#');
if (ci >= 0)
line = line.substring(0, ci);
line = line.trim();
if (line.length() > 0) {
try {
String name = null;
int i = line.indexOf('=');
if (i > 0) {
// 解析出 name 和 实现类
name = line.substring(0, i).trim();
line = line.substring(i + 1).trim();
}
if (line.length() > 0) {
loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
exceptions.put(line, e);
}
}
}
} finally {
reader.close();
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t);
}
}

  这里是解析配置文本文件的内容,通过反射获得Class,再调用loadClass(),我们接着往下

 private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
// type是否为clazz的超类,clazz是否实现了type接口
// 此处clazz 是扩展实现类的Class
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface.");
}
// clazz是否注解了 Adaptive 自适应扩展
// 不允许多个类注解Adaptive
// 注解adaptive的实现类,赋值给cachedAdaptiveClass
if (clazz.isAnnotationPresent(Adaptive.class)) {
if (cachedAdaptiveClass == null) {
cachedAdaptiveClass = clazz;
// 不允许多个实现类都注解@Adaptive
} else if (!cachedAdaptiveClass.equals(clazz)) {
throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName());
}
// 是否为包装类,判断扩展类是否提供了参数是扩展点的构造函数
} else if (isWrapperClass(clazz)) {
Set<Class<?>> wrappers = cachedWrapperClasses;
if (wrappers == null) {
cachedWrapperClasses = new ConcurrentHashSet<Class<?>>();
wrappers = cachedWrapperClasses;
}
wrappers.add(clazz);
// 普通扩展类
} else {
// 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常
clazz.getConstructor();
// 此处name为 SPI配置中的key
// @SPI配置中key可以为空,此时key为扩展类的类名(getSimpleName())小写
if (name == null || name.length() == 0) {
// 兼容旧版本
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
// 逗号分割
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
// 获取Activate注解
Activate activate = clazz.getAnnotation(Activate.class);
if (activate != null) {
cachedActivates.put(names[0], activate);
}
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
// name不能重复
Class<?> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName());
}
}
}
}
}

  我们在第10行,终于看到了Adaptive注解判断。

  如果扩展点实现类存在@Adaptive注解,Class对象赋值给cachedAdaptiveClass,并且在第14行判断是否存在多个类都是@Adaptive注解,如果同一个扩展点的多个实现类都有@Adaptive注解,则抛出异常。

  到这里,我们看到了扩展点自适应扩展点的类级别注解的调用及返回逻辑。其实前面也说过了,@Adaptive修饰类的场景并不多,也不是重点,重点是@Adaptive修饰方法的时候。

  我们返回到getAdaptiveExtensionClass()方法,为了清晰,我们再看看这个方法的代码

 private Class<?> getAdaptiveExtensionClass() {
// 通过SPI获取所有的扩展类,赋值相关的成员变量
getExtensionClasses();
// 如果有@Adaptive修饰的类,cachedAdaptiveClass不为空
if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass;
}
// 没有@Adaptive修饰的类时,根据@Adaptive修饰方法 创建自适应扩展类
return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

  通过前面的分析,如果@Adaptive没有修饰类,则cachedAdaptiveClass 为空,此时,我们会进入createAdaptiveExtensionClass(),这个方法是实现@Adaptive修饰方法的逻辑实现,也是自适应扩展的重点所在。

  我们来看createAdaptiveExtensionClass这个方法

 private Class<?> createAdaptiveExtensionClass() {
// 创建自适应扩展代码 字符串
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
// 获取编译器实现类
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
// 编译代码,获取自适应扩展类的Class
return compiler.compile(code, classLoader);
}

  这个方法实现的功能主要两点:

  1、通过createAdaptiveExtensionClassCode()获取创建的新类字符串

  2、通过Compiler编译器,编译新类字符串,获得新类的Class对象

  所以,我们的重点是分析新类字符串的实现逻辑,这也是自适应扩展的重点。

  我们接着看createAdaptiveExtensionClassCode()方法,这个方法有300多行,我们分段来看

 private String createAdaptiveExtensionClassCode() {
StringBuilder codeBuilder = new StringBuilder();
// 通过反射获取所有方法
Method[] methods = type.getMethods();
boolean hasAdaptiveAnnotation = false;
// 遍历方法,判断至少有一个方法被@Adaptive修饰
for (Method m : methods) {
if (m.isAnnotationPresent(Adaptive.class)) {
hasAdaptiveAnnotation = true;
break;
}
}
// no need to generate adaptive class since there's no adaptive method found.
// 没有被@Adaptive修饰的方法,抛出异常
if (!hasAdaptiveAnnotation)
throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
// 生成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(" {");
...............
...暂时省略....
..............
}

  方法开始的逻辑很简单,拿到扩展点type的方法,循环方法列表,判断是否存在一个方法是被@Adaptive注解的,如果存在则继续,否则抛出异常。这也符合正常的逻辑,如果所有的方法都没@Adaptive注解,那么获取自适应扩展就没有意义了。

  从15行开始进行新类字符串的构造,我们看到了关键字”package“、”import“、”class“等,执行到22行处,生成的代码字符串,我们以扩展点Protocol为例,展示一下:

 package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol {
// 省略方法代码
}

  这个类就是我们生成的新的扩展点实现类,我们可以看到类名以及实现的接口。
  我们接着往下分析,前面我们说过,扩展点方法分为两种,一个是有@Adaptive注解的,一个是无@Adaptive注解的,既然实现了扩展点接口,这两种方法都要在新类中实现。
  我们首先分析没有@Adaptive注解的方法。

 private String createAdaptiveExtensionClassCode() {
...............
...暂时省略....
..............
// 生成方法
for (Method method : methods) {
// 方法返回类型
Class<?> rt = method.getReturnType();
// 方法参数数组
Class<?>[] pts = method.getParameterTypes();
// 方法异常数组
Class<?>[] ets = method.getExceptionTypes();
// 方法的Adaptive注解
Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
StringBuilder code = new StringBuilder(512);
// 没有@Adaptive注解的方法,生成的方法体内为 throw 异常
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 {
...............
...暂时省略....
..............
}

  通过for循环,进行逐个方法生成。

  在第14行,获取到方法的@Adaptive注解,第17行进行判断,如果为空,生成的代码是抛出一个异常,示例如下:

 throw new UnsupportedOperationException(
"method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");

  我们接着分析存在@Adaptive注解的方法,生成代码的逻辑

 ...暂时省略....
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 {
// 有@Adaptive注解的方法,参数中必须有URL,或是可以从方法参数中获取URL
int urlTypeIndex = -1;
for (int i = 0; i < pts.length; ++i) {
if (pts[i].equals(URL.class)) {
urlTypeIndex = i;
break;
}
}
// found parameter in URL type
// 方法中存在URL
if (urlTypeIndex != -1) {
// Null Point check
// 为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);
}
...暂时省略....

  第10行,循环的变量pts 为方法参数数组,如果参数数组中有URL类型,数组下标赋值给urlTypeIndex,跳出循环。

  第18行进行urlTypeIndex 判断,此时如果不为-1,说明方法参数数组中存在URL类型的参数,生成的代码首先是非空判断,接着就是把对应的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();
// 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;
}
}
}
...暂时省略....

  上面的代码是参数数组中不存在URL类型参数的情况。如果不存在URL类型的参数,就需要从所有的入参中判断,参数对象中是否可以通过get方法 获取到URL对象。如果不可以则抛出异常。

  标签LBL_PTS用于结束最外部的循环。我们看到最外边的循环,还是参数数组pts。

  第8行是拿到参数数组中单个参数的所有方法,再进行循环,判断是否存在满足如下条件: 1、方法名以get开头,或方法名大于3个字符;2、方法的访问权限是public; 3、非静态方法;4、方法参数数量为0; 5、方法返回值类型为URL的方法,如果存在赋值方法名给attribMethod ,跳出最外变循环。

  我们接着往下看

 ...暂时省略....
// 如果参数中都不包含可返回的URL的get方法,抛出异常
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());
}
...暂时省略....

  我们看到,如果attribMethod 为空,也就是前面的两个循环没有找到存在返回URL方法的参数对象,直接抛出异常,方法结束执行。

  如果attribMethod 不为空,即存在返回URL方法的参数对象,我们再往下看:

 ...暂时省略....
// Null point check
// 为可返回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);
...暂时省略....

  第6行生成新类代码是空判断,如果参数数组下标为urlTypeIndex的参数为空,抛出异常。

  第13行是返回URL类型方法的空判断,我们知道参数数组下标是urlTypeIndex的参数,存在返回URL类型的方法,方法名为attribMethod,此处就是判断方法attribMethod是否为空null。

  第20行就是执行attribMethod,赋值给url变量。

  至此,从入参中获得了URL类型的变量。前面是直接从参数数组中获取类型为URL的参数,后面是从参数数组中的某个可以返回URL参数方法的参数。两种方式目的就是获取到URL类型的变量,这个是必须的,因为自适应的扩展,获取扩展点的key是从URL中解析出来的。

  在获取到URL类型的变量后,现在就要获取关键字key了,根据key从URL中获取的value,就是自适应扩展点在配置文本文件中对应的key。

  我们接着往下看

 ...暂时省略....
// 获取方法@Adaptive的注解值
String[] value = adaptiveAnnotation.value();
// value is not set, use the value generated from class name as the key
// @Adaptive的value为空,需要特殊处理
// 将类名转换为字符数组,然后遍历字符数组,并将字符存入StringBulder
// 若字符为大写字母,则向StringBuiilder中添加“.”,随后字符变为小写存入StringBuilder
// 比如LoadBalance经过处理,得到load.balance
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() };
}
...暂时省略....

  第3行,是直接从@Adaptive注解中获取value,类型为字符串数组。

  如果@Adaptive注解没有设置value的值,接着看第9行的判断。

  从第11行开始,自动生成从URL获取自适应扩展关键字的key。生成的逻辑是根据扩展点type的名称,遍历type名称的字符数组,除了首字符,遇到大写的字符前面加“.",大写转小写,组装的字符串就是要获取的value值。

  我们接着往下看

 ...暂时省略....
// 检测方法列表中是否存在Invocation类型的参数
// 若存在,则为其生成判空代码和其他一些代码
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")) {
// Null Point check
// 为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;
}
}
...暂时省略....

  这段代码选好pts,判断是否存在类型为Invocation的参数。如果存在生成为空判断,之后从Invocation类型的参数中获取methodName。并设置hasInvocation为true。

 ...暂时省略....
/**
* 根据SPI和Adaptive注解值生成“获取扩展名逻辑”,同时生成逻辑也受Invocation类型参数影响 生成格式如: String extName =
* url.getMethodParameter(methodName, "loadbalance","random");
*/
// 设置默认扩展名,cachedDefaultName源于SPI注解值,默认情况下,
// SPI注解值为空串,此时cachedDefaultName 为 null
String defaultExtName = cachedDefaultName;
String getNameCode = null;
// 遍历value,value是Adaptive的注解值,上面有value的获取过程
// 此处循环的目的是生成从URL中获取扩展名的代码,生成的代码会赋值给getNameCode变量
// 这个循环的遍历顺序是由后向前遍历的
for (int i = value.length - 1; i >= 0; --i) {
// i为最后一个元素的坐标时
if (i == value.length - 1) {
// 默认扩展名非空
if (null != defaultExtName) {
// protocol是扩展名的一部分,可以通过getProtocol方法获取,其他则是从URL参数中获取
// 因为获取方式不同,因此要进行判断
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);
}
}
}
...暂时省略....

  第8行获取默认扩展名。来自于@SPI注解的value值。

  第13行开始循环value,此处的value是@Adaptive注解的内容,前面有过分析,如果@Adaptive没有设置value,则通过type名称解析出value。

  上面的代码分支比较多,但是主要是集中在protocol、hasInvocation的判断上。

  首先看hasInvocation,如果hasInvocation不为空,我们看到生成的代码是“url.getMethodParameter(methodName...”,这个methodName也是前面判断hasInvocation时获取到的。这等于在从URL中获取值的时候,加上了methodName。如果hasInvocation为空,此时的分支生成的代码,直接是“url.getParameter(.."。
第二个是判断protocol的分支,由于URL类中有单独的protocol变量,所以 如果value值为protocol,此时从URL中取值,可以直接调用url.getProtocol(),不需要通过URL的getParameter方法。
  上面的代码是从URL中获取扩展点key的主要逻辑,分支比较多,但是很多重复的代码,也进行了比较详细的注释。

  我们接着往下看,从URL中拿到扩展点的key后的代码

 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);
// 生成扩展获取代码,格式如下:
// 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); // return statement
// 如果方法返回值类型非 void,则生成 return 语句。
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(");");

  这段代码就比较简单明了了,核心在第11行,强制转换为扩展点type类型,通过ExtensionLoader的getExtensionLoader获取type接口对应的ExtensionLoader实例。

  现在已经拿到的扩展点实现的key,只要调用ExtensionLoader实例的getExtension()方法,即可返回需要调用的扩展点实现。

  我们分析的主线是按扩展点的一个方法进行,每个被@Adaptive修饰的方法,生成的逻辑都是一样的,主要的逻辑是:

  1、根据@Adaptive注解的value,或是扩展点type的名称生成从URL获取扩展点实现类key的关键字
  2、根据第一步获取的关键字,从URL中获取要调用的扩展点实现类的key
  3、获取到扩展点实现类对应的key,调用ExtensionLoader实例的getExtension()方法,即可拿到对应的扩展点实现
  4、方法的执行是调用扩展点实现类的目标方法。

  至此新类的字符串已经生成了,我们回到createAdaptiveExtensionClass方法

 private Class<?> createAdaptiveExtensionClass() {
// 创建自适应扩展代码 字符串
String code = createAdaptiveExtensionClassCode();
ClassLoader classLoader = findClassLoader();
// 获取编译器实现类
com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader
.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
// 编译代码,获取自适应扩展类的Class
return compiler.compile(code, classLoader);
}

  第3行就是我们前面分析的获取新类字符串的方法,拿到code之后,再获取类加载器,获取编辑器,执行编译。返回的就是自适应扩展类的Class对象。

  通过此方法,再往上返回就是自适应扩展类的对象,以及缓存对象等逻辑。自适应扩展的获取基本就结束了。

四、总结

  通过上面的分析,我们基本了解的自适应扩展点的实现逻辑,难点就是@Adaptive注解方法时,生成新类的字符串之处。别的逻辑还算清晰。如果在读到此处有困惑,请评论留言,我会进行详细解释。

Dubbo源码分析之SPI(三)的更多相关文章

  1. dubbo源码分析2——SPI机制中的SPI实现类的读取和预处理

    SPI机制中的SPI实现类的读取和预处理是由ExtensionLoader类的loadFile方法来完成的 loadFile方法的作用是读取dubbo的某个SPI接口的spi描述文件,然后进行缓存,缓 ...

  2. Dubbo源码分析之 SPI(一)

    一.概述 dubbo SPI 在dubbo的作用是基础性的,要想分析研究dubbo的实现原理.dubbo源码,都绕不过 dubbo SPI,掌握dubbo SPI 是征服dubbo的必经之路. 本篇文 ...

  3. Dubbo源码分析之SPI(二)

    一.概述 本篇文章是dubbo SPI源码分析的第二篇,接着第一篇继续分析dubbo SPI的内容,我们主要介绍 getDefaultExtension() 获取默认扩展点方法. 由于此方法比较简单, ...

  4. dubbo源码分析4——SPI机制_ExtensionFactory类的作用

    ExtensionFactory的源码: @SPI public interface ExtensionFactory { /** * Get extension. * * @param type o ...

  5. dubbo源码分析6——SPI机制中的AOP

    在 ExtensionLoader 类的loadFile方法中有下图的这段代码: 类如现在这个ExtensionLoader中的type 是Protocol.class,也就是SPI接口的实现类中Xx ...

  6. dubbo源码分析5——SPI机制_AdaptiveExtension的原理和作用

    private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().n ...

  7. dubbo源码分析3——SPI机制中的ExtensionLoader类的objectFactory属性分析

    ExtensionLoader类是整个SPI的核心类,每个SPI都会对应一个ExtensionLoader类实例,这个类的构造方法如下: private ExtensionLoader(Class&l ...

  8. dubbo源码分析1——SPI机制的概要介绍

    插件机制是Dubbo用于可插拔地扩展底层的一些实现而定制的一套机制,比如dubbo底层的RPC协议.注册中心的注册方式等等.具体的实现方式是参照了JDK的SPI思想,由于JDK的SPI的机制比较简单, ...

  9. Dubbo 源码分析 - 服务调用过程

    注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章. 1. 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与引入.以及集群容错方面的代码.经过 ...

随机推荐

  1. django_4数据库2——表外键

    表关系: many to one many to many one to one many to one 记录是如何创建出来的?      先创建one,在在创建many,创建时加入ForeignKe ...

  2. android灭屏后调用binder通讯竟然影响了socket的POLL_OUT事件,怪事。

    当你的android在灭屏(休眠)时分派(dispatch) Ice调用过程中,如果创建了新的进程,你的响应将不会预期那样工作,尽管你已经调用 ice_response或 ice_exception, ...

  3. opencv 5 图像转换(3 重映射 仿射变换 直方图均衡化)

    重映射 实现重映射(remap函数) 基础示例程序:基本重映射 //---------------------------------[头文件.命名空间包含部分]------------------- ...

  4. 2019-11-3:渗透测试,基础学习,bypass类型笔记

    等价字符 空格:%20,+,(),%0a,%09,%a0,%0b,%0c,%0d,/**/等 =:like,regexp,liker,<>,! =等 and:&& or:x ...

  5. 新闻实时分析系统 大数据Web可视化分析系统开发

    1.基于业务需求的WEB系统设计 2.下载Tomcat并创建Web工程并配置相关服务 下载tomcat,解压并启动tomcat服务. 1)新建web app项目 创建好之后的效果 2)对tomcat进 ...

  6. Spring中常见的设计模式——原型模式

    1.原型模式应用场景 当遇到大量耗费劳动力的 get,set赋值场景时,如下: public class SetGetParam { public void setParam(UserDto user ...

  7. pymongo的基本操作和使用--练习

    1.将MongoDB注册到电脑中 安装好MongoDB之后,如何使用MongoDB呢?来到安装目录D:/MongoDB/bin会有如下列表: 其中,mongod.exe是服务端,mongo.exe是客 ...

  8. iptables简单命令

    IPTables是基于Netfilter基本架构实现的一个可扩展的数据报高级管理系统或核外配置工具,利用table.chain.rule三级来存储数据报的各种规则.Netfilter-iptables ...

  9. SpringMVC 请求/响应乱码问题解决方案

    请求乱码解决之get乱码问题 GET请求乱码原因分析 GET请求参数是通过请求行中的URL发送给Web服务器(Tomcat)的. Tomcat服务器会对URL进行编码操作(此时使用的是Tomcat设置 ...

  10. python小知识课堂

    啦啦啦 with上下文管理 __class__和type的关系