一、概述

  本篇介绍自适应扩展,方法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. WordPress 去掉底部的自豪的采用WordPress

    WordPress 去掉底部的自豪的采用WordPress  

  2. 【ABP】 动态菜单修改过程asp.netcore+vue

    无论用什么框架,第一件事情就是实现动态菜单,从数据库中读取菜单配置项输出前台,网上翻了一大堆翻译文档,也看了官方英文文档,关键点在于如何实现NavigationProvider和在前端调用abp.na ...

  3. svn文件被锁不能提交的解决办法

    记录工作中遇到的问题,分享出来: 前端时间在提交项目到svn遇到一个问题, 提交的时候提示:文件已经锁定!如下图: 原因是我之前提交的时候不小心中途停了,导致文件被锁,然后也没在意那么多, 趁着今天有 ...

  4. workspaces only allow trusted client with self-signed cert

    1. 生成ca. openssl genrsa -out CA_neonone.workspace.key 2048 openssl req -x509 -new -nodes -key CA_neo ...

  5. IDEA最常用快捷键汇总+快速写出Main函数

    IDEA可以说是当下Java程序员日常开发的神器,但是想要发挥这款神器的牛逼威力,必须得熟练使用它的各种快捷键才行.本篇总结下使用IDEA(也就是IntelliJ IDEA )进行日常开发中最常用的快 ...

  6. Spring Cloud Alibaba(四)实现Dubbo服务消费

    本项目演示如何使用 Spring Cloud Alibaba 完成 Dubbo 的RPC调用. Spring Cloud与Dubbo Spring Cloud是一套完整的微服务架构方案 Dubbo是国 ...

  7. Dart Learn Notes 02

    Functions Dart是一门面向对象的语言,所以即便是方法也是一个对象,它的类型是Function. 这就意味着方法可以指向变量,也可以作为方法中的参数供其他方法使用.甚至可以让 一个类作为一个 ...

  8. 今天是python专场UDP socket 链接

    type = SOCK_DGRAM UDP 协议的通信优势 允许一个服务器的同时和多个客户端通信 server import socket sk = socket.socket(type=socket ...

  9. ArrayList实现原理(JDK1.8)

    ArrayList实现原理(JDK1.8) public class ArrayList<E> extends AbstractList<E> implements List& ...

  10. python网络爬虫之自动化测试工具selenium[二]

    目录 前言 一.获取今日头条的评论信息(request请求获取json) 1.分析数据 2.获取数据 二.获取今日头条的评论信息(selenium请求获取) 1.分析数据 2.获取数据 房源案例(仅供 ...