一、概述
本篇介绍自适应扩展,方法getAdaptiveExtension()的实现。ExtensionLoader类本身很多功能也使用到了自适应扩展。包括ExtensionFactory扩展。
通俗的讲,自适应扩展要实现的逻辑是:调用扩展点的方法时,自动判断要调用那个扩展点实现类的方法。我们知道,一个扩展点通常有多个实现类,在配置文本文件中分多行配置,在前面的分析中,我们知道通过getExtension(String name)方法,返回的是指定key的扩展点,而自适应扩展点方法getAdaptiveExtension()在调用前,不确认返回那个扩展点。而是在方法调用的时候,根据方法入参,进行确定,具体是调用那个实现类。
自适应扩展基于@Adaptive注解,可以修饰类,也可以修饰方法。修饰类的时候,逻辑比较简单,不会动态生成代码逻辑,使用的场景也比较少,主要包括AdaptiveCompiler 和 AdaptiveExtensionFactory。修饰方法的时候,会动态生成一个新类,新类包括扩展点的所有方法,调用getAdaptiveExtension()返回的就是新类对象。
二、详细介绍
前面我们说过,@Adaptive可以修饰类,也可以修饰方法。我们先看下修饰类的场景。
通过一个具体的实现类来看下,这里我们分析AdaptiveCompiler类的实现:
1 @Adaptive 2 public class AdaptiveCompiler implements Compiler { 3 4 private static volatile String DEFAULT_COMPILER; 5 6 public static void setDefaultCompiler(String compiler) { 7 DEFAULT_COMPILER = compiler; 8 } 9 10 @Override 11 public Class> compile(String code, ClassLoader classLoader) { 12 Compiler compiler; 13 ExtensionLoaderloader = ExtensionLoader.getExtensionLoader(Compiler.class); 14 String name = DEFAULT_COMPILER; // copy reference 15 if (name != null && name.length() > 0) { 16 compiler = loader.getExtension(name); 17 } else { 18 compiler = loader.getDefaultExtension(); 19 } 20 return compiler.compile(code, classLoader); 21 } 22 23 }
此类,只有一个对外提供的方法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()方法开始
1 public T getAdaptiveExtension() { 2 // 从缓存中获取自定义拓展 3 Object instance = cachedAdaptiveInstance.get(); 4 if (instance == null) { 5 if (createAdaptiveInstanceError == null) { 6 synchronized (cachedAdaptiveInstance) { 7 instance = cachedAdaptiveInstance.get(); 8 if (instance == null) { 9 try { 10 instance = createAdaptiveExtension(); 11 cachedAdaptiveInstance.set(instance); 12 } catch (Throwable t) { 13 createAdaptiveInstanceError = t; 14 throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); 15 } 16 } 17 } 18 } else { 19 throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); 20 } 21 } 22 23 return (T) instance; 24 }
这个方法的逻辑很简单,主要包括
1、从缓存对象cachedAdaptiveInstance获取自适应扩展点实例
2、缓存有直接返回,没有进行方法createAdaptiveExtension()调用
3、根据方法返回的实例,设置到到缓存里,并进行返回
所以我们接着分析createAdaptiveExtension方法
1 private T createAdaptiveExtension() { 2 try { 3 // injectExtension 为@Adaptive注解的类 可能存在的IOC服务 4 // @Adaptive注解方法 自动生成的代理类不存在IOC可能 5 T instance = (T) getAdaptiveExtensionClass().newInstance(); 6 return injectExtension(instance); 7 } catch (Exception e) { 8 throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e); 9 } 10 }
可以看到,方法内部是通过getAdaptiveExtensionClass() 获取到Class实例,再反射实例化,获取到实例对象。
我们接着往下看getAdaptiveExtensionClass()方法
1 private Class> getAdaptiveExtensionClass() { 2 // 通过SPI获取所有的扩展类,赋值相关的成员变量 3 getExtensionClasses(); 4 // 如果有@Adaptive修饰的类,cachedAdaptiveClass不为空 5 if (cachedAdaptiveClass != null) { 6 return cachedAdaptiveClass; 7 } 8 // 没有@Adaptive修饰的类时,根据@Adaptive修饰方法 创建自适应扩展类 9 return cachedAdaptiveClass = createAdaptiveExtensionClass(); 10 }
首先执行的是getExtensionClasses()方法,之后判断cachedAdaptiveClass 是否为空,不为空就直接返回了。这个cachedAdaptiveClass 变量,其实就是有@Adaptive修饰的扩展点实现。也就是说,如果在扩展点的实现类中,存在@Adaptive修饰的类,就直接返回这个类了。
那么cachedAdaptiveClass 在是哪里赋值的呢?我们需要再看getExtensionClasses()方法。getExtensionClasses这个方法在前面两篇文章中已经都有介绍。在默认扩展点的实现里面,cachedDefaultName变量的赋值就是在这个方法里进行的。cachedAdaptiveClass 的赋值的方法调用链我们这里直接给出来
1 getExtensionClasses()-->loadExtensionClasses()-->loadDirectory()-->loadResource()-->loadClass()
隐藏的比较深,第5个方法才对cachedDefaultName进行了赋值。
我们一步一步来分析,先看getExtensionClasses()
1 private Map> getExtensionClasses() { 2 Map > classes = cachedClasses.get(); 3 if (classes == null) { 4 synchronized (cachedClasses) { 5 classes = cachedClasses.get(); 6 if (classes == null) { 7 classes = loadExtensionClasses(); 8 cachedClasses.set(classes); 9 } 10 } 11 } 12 return classes; 13 }
这个方法很简单,我们接着看loadExtensionClasses()
1 private Map> loadExtensionClasses() { 2 // 获取注解 SPI的接口 3 // type为传入的扩展接口,必须有@SPI注解 4 final SPI defaultAnnotation = type.getAnnotation(SPI.class); 5 // 获取默认扩展实现value,如果存在,赋值给cachedDefaultName 6 if (defaultAnnotation != null) { 7 String value = defaultAnnotation.value(); 8 if ((value = value.trim()).length() > 0) { 9 // @SPI value 只能是一个,不能为逗号分割的多个 10 // @SPI value为默认的扩展实现 11 String[] names = NAME_SEPARATOR.split(value); 12 if (names.length > 1) { 13 throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names)); 14 } 15 if (names.length == 1) 16 cachedDefaultName = names[0]; 17 } 18 } 19 // 加载三个目录配置的扩展类 20 Map > extensionClasses = new HashMap >(); 21 // META-INF/dubbo/internal 22 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); 23 // META-INF/dubbo 24 loadDirectory(extensionClasses, DUBBO_DIRECTORY); 25 // META-INF/services/ 26 loadDirectory(extensionClasses, SERVICES_DIRECTORY); 27 return extensionClasses; 28 }
很熟悉吧,这个方法我们在第一篇文章中已经有介绍了,我们再接着往下看loadDirectory方法
1 private void loadDirectory(Map> extensionClasses, String dir) { 2 // 扩展配置文件完整文件路径+文件名 3 String fileName = dir + type.getName(); 4 try { 5 6 Enumeration urls; 7 // 获取类加载器 8 ClassLoader classLoader = findClassLoader(); 9 if (classLoader != null) { 10 urls = classLoader.getResources(fileName); 11 } else { 12 urls = ClassLoader.getSystemResources(fileName); 13 } 14 if (urls != null) { 15 while (urls.hasMoreElements()) { 16 java.net.URL resourceURL = urls.nextElement(); 17 // 加载 18 loadResource(extensionClasses, classLoader, resourceURL); 19 } 20 } 21 } catch (Throwable t) { 22 logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t); 23 } 24 }
这个方法我们也很分析过了,再往下看loadResource()方法
1 private void loadResource(Map> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { 2 try { 3 BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8")); 4 try { 5 String line; 6 while ((line = reader.readLine()) != null) { 7 // 字符#是注释开始标志,只取#前面的字符 8 final int ci = line.indexOf('#'); 9 if (ci >= 0) 10 line = line.substring(0, ci); 11 line = line.trim(); 12 if (line.length() > 0) { 13 try { 14 String name = null; 15 int i = line.indexOf('='); 16 if (i > 0) { 17 // 解析出 name 和 实现类 18 name = line.substring(0, i).trim(); 19 line = line.substring(i + 1).trim(); 20 } 21 if (line.length() > 0) { 22 loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); 23 } 24 } catch (Throwable t) { 25 IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t); 26 exceptions.put(line, e); 27 } 28 } 29 } 30 } finally { 31 reader.close(); 32 } 33 } catch (Throwable t) { 34 logger.error("Exception when load extension class(interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t); 35 } 36 }
这里是解析配置文本文件的内容,通过反射获得Class,再调用loadClass(),我们接着往下
1 private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class> clazz, String name) throws NoSuchMethodException { 2 // type是否为clazz的超类,clazz是否实现了type接口 3 // 此处clazz 是扩展实现类的Class 4 if (!type.isAssignableFrom(clazz)) { 5 throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface."); 6 } 7 // clazz是否注解了 Adaptive 自适应扩展 8 // 不允许多个类注解Adaptive 9 // 注解adaptive的实现类,赋值给cachedAdaptiveClass 10 if (clazz.isAnnotationPresent(Adaptive.class)) { 11 if (cachedAdaptiveClass == null) { 12 cachedAdaptiveClass = clazz; 13 // 不允许多个实现类都注解@Adaptive 14 } else if (!cachedAdaptiveClass.equals(clazz)) { 15 throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); 16 } 17 // 是否为包装类,判断扩展类是否提供了参数是扩展点的构造函数 18 } else if (isWrapperClass(clazz)) { 19 Set > wrappers = cachedWrapperClasses; 20 if (wrappers == null) { 21 cachedWrapperClasses = new ConcurrentHashSet >(); 22 wrappers = cachedWrapperClasses; 23 } 24 wrappers.add(clazz); 25 // 普通扩展类 26 } else { 27 // 检测 clazz 是否有默认的构造方法,如果没有,则抛出异常 28 clazz.getConstructor(); 29 // 此处name为 SPI配置中的key 30 // @SPI配置中key可以为空,此时key为扩展类的类名(getSimpleName())小写 31 if (name == null || name.length() == 0) { 32 // 兼容旧版本 33 name = findAnnotationName(clazz); 34 if (name.length() == 0) { 35 throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); 36 } 37 } 38 // 逗号分割 39 String[] names = NAME_SEPARATOR.split(name); 40 if (names != null && names.length > 0) { 41 // 获取Activate注解 42 Activate activate = clazz.getAnnotation(Activate.class); 43 if (activate != null) { 44 cachedActivates.put(names[0], activate); 45 } 46 for (String n : names) { 47 if (!cachedNames.containsKey(clazz)) { 48 cachedNames.put(clazz, n); 49 } 50 // name不能重复 51 Class> c = extensionClasses.get(n); 52 if (c == null) { 53 extensionClasses.put(n, clazz); 54 } else if (c != clazz) { 55 throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); 56 } 57 } 58 } 59 } 60 }
我们在第10行,终于看到了Adaptive注解判断。
如果扩展点实现类存在@Adaptive注解,Class对象赋值给cachedAdaptiveClass,并且在第14行判断是否存在多个类都是@Adaptive注解,如果同一个扩展点的多个实现类都有@Adaptive注解,则抛出异常。
到这里,我们看到了扩展点自适应扩展点的类级别注解的调用及返回逻辑。其实前面也说过了,@Adaptive修饰类的场景并不多,也不是重点,重点是@Adaptive修饰方法的时候。
我们返回到getAdaptiveExtensionClass()方法,为了清晰,我们再看看这个方法的代码
1 private Class> getAdaptiveExtensionClass() { 2 // 通过SPI获取所有的扩展类,赋值相关的成员变量 3 getExtensionClasses(); 4 // 如果有@Adaptive修饰的类,cachedAdaptiveClass不为空 5 if (cachedAdaptiveClass != null) { 6 return cachedAdaptiveClass; 7 } 8 // 没有@Adaptive修饰的类时,根据@Adaptive修饰方法 创建自适应扩展类 9 return cachedAdaptiveClass = createAdaptiveExtensionClass(); 10 }
通过前面的分析,如果@Adaptive没有修饰类,则cachedAdaptiveClass 为空,此时,我们会进入createAdaptiveExtensionClass(),这个方法是实现@Adaptive修饰方法的逻辑实现,也是自适应扩展的重点所在。
我们来看createAdaptiveExtensionClass这个方法
1 private Class> createAdaptiveExtensionClass() { 2 // 创建自适应扩展代码 字符串 3 String code = createAdaptiveExtensionClassCode(); 4 ClassLoader classLoader = findClassLoader(); 5 // 获取编译器实现类 6 com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); 7 // 编译代码,获取自适应扩展类的Class 8 return compiler.compile(code, classLoader); 9 }
这个方法实现的功能主要两点:
1、通过createAdaptiveExtensionClassCode()获取创建的新类字符串
2、通过Compiler编译器,编译新类字符串,获得新类的Class对象
所以,我们的重点是分析新类字符串的实现逻辑,这也是自适应扩展的重点。
我们接着看createAdaptiveExtensionClassCode()方法,这个方法有300多行,我们分段来看
1 private String createAdaptiveExtensionClassCode() { 2 StringBuilder codeBuilder = new StringBuilder(); 3 // 通过反射获取所有方法 4 Method[] methods = type.getMethods(); 5 boolean hasAdaptiveAnnotation = false; 6 // 遍历方法,判断至少有一个方法被@Adaptive修饰 7 for (Method m : methods) { 8 if (m.isAnnotationPresent(Adaptive.class)) { 9 hasAdaptiveAnnotation = true; 10 break; 11 } 12 } 13 // no need to generate adaptive class since there's no adaptive method found. 14 // 没有被@Adaptive修饰的方法,抛出异常 15 if (!hasAdaptiveAnnotation) 16 throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!"); 17 // 生成package代码:package+type所在包 18 codeBuilder.append("package ").append(type.getPackage().getName()).append(";"); 19 // 生成import代码:import+ExtensionLoader权限定名 20 codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";"); 21 // 生成类代码:public class + type简单名称+$Adaptive+implements + type权限定名+{ 22 codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {"); 23 ............... 24 ...暂时省略.... 25 .............. 26 }
方法开始的逻辑很简单,拿到扩展点type的方法,循环方法列表,判断是否存在一个方法是被@Adaptive注解的,如果存在则继续,否则抛出异常。这也符合正常的逻辑,如果所有的方法都没@Adaptive注解,那么获取自适应扩展就没有意义了。
从15行开始进行新类字符串的构造,我们看到了关键字”package“、”import“、”class“等,执行到22行处,生成的代码字符串,我们以扩展点Protocol为例,展示一下:
1 package com.alibaba.dubbo.rpc; 2 import com.alibaba.dubbo.common.extension.ExtensionLoader; 3 public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol { 4 // 省略方法代码 5 }
这个类就是我们生成的新的扩展点实现类,我们可以看到类名以及实现的接口。
我们接着往下分析,前面我们说过,扩展点方法分为两种,一个是有@Adaptive注解的,一个是无@Adaptive注解的,既然实现了扩展点接口,这两种方法都要在新类中实现。
我们首先分析没有@Adaptive注解的方法。
1 private String createAdaptiveExtensionClassCode() { 2 ............... 3 ...暂时省略.... 4 .............. 5 // 生成方法 6 for (Method method : methods) { 7 // 方法返回类型 8 Class> rt = method.getReturnType(); 9 // 方法参数数组 10 Class>[] pts = method.getParameterTypes(); 11 // 方法异常数组 12 Class>[] ets = method.getExceptionTypes(); 13 // 方法的Adaptive注解 14 Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); 15 StringBuilder code = new StringBuilder(512); 16 // 没有@Adaptive注解的方法,生成的方法体内为 throw 异常 17 if (adaptiveAnnotation == null) { 18 // throw new UnsupportedOperationException( 19 // "method " + 方法签名 + of interface + 全限定接口名 + is not adaptive method!”) 20 code.append("throw new UnsupportedOperationException(\"method ").append(method.toString()).append(" of interface ").append(type.getName()).append(" is not adaptive method!\");"); 21 } else { 22 ............... 23 ...暂时省略.... 24 .............. 25 }
通过for循环,进行逐个方法生成。
在第14行,获取到方法的@Adaptive注解,第17行进行判断,如果为空,生成的代码是抛出一个异常,示例如下:
1 throw new UnsupportedOperationException( 2 "method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
我们接着分析存在@Adaptive注解的方法,生成代码的逻辑
1 ...暂时省略.... 2 if (adaptiveAnnotation == null) { 3 // throw new UnsupportedOperationException( 4 // "method " + 方法签名 + of interface + 全限定接口名 + is not adaptive method!”) 5 code.append("throw new UnsupportedOperationException(\"method ").append(method.toString()) 6 .append(" of interface ").append(type.getName()).append(" is not adaptive method!\");"); 7 } else { 8 // 有@Adaptive注解的方法,参数中必须有URL,或是可以从方法参数中获取URL 9 int urlTypeIndex = -1; 10 for (int i = 0; i < pts.length; ++i) { 11 if (pts[i].equals(URL.class)) { 12 urlTypeIndex = i; 13 break; 14 } 15 } 16 // found parameter in URL type 17 // 方法中存在URL 18 if (urlTypeIndex != -1) { 19 // Null Point check 20 // 为URL类型参数判断空代码,格式如下: 21 // if (arg + urlTypeIndex == null) 22 // throw new IllegalArgumentException("url == null"); 23 String s = String.format( 24 "\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");", urlTypeIndex); 25 code.append(s); 26 // 为URL类型参数生成赋值代码,例如:URL url = arg1; 27 s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); 28 code.append(s); 29 } 30 ...暂时省略....
第10行,循环的变量pts 为方法参数数组,如果参数数组中有URL类型,数组下标赋值给urlTypeIndex,跳出循环。
第18行进行urlTypeIndex 判断,此时如果不为-1,说明方法参数数组中存在URL类型的参数,生成的代码首先是非空判断,接着就是把对应的URL类型参数就行变量赋值。
接着往下看
1 ...暂时省略.... 2 else { 3 String attribMethod = null; 4 // find URL getter method 5 // 遍历方法的参数类型 6 LBL_PTS: for (int i = 0; i < pts.length; ++i) { 7 // 方法参数类型的方法数组 8 Method[] ms = pts[i].getMethods(); 9 for (Method m : ms) { 10 String name = m.getName(); 11 // 1、方法名以get开头,或方法名大于3个字符 12 // 2、方法的访问权限是public 13 // 3、非静态方法 14 // 4、方法参数数量为0 15 // 5、方法返回值类型为URL 16 if ((name.startsWith("get") || name.length() > 3) && Modifier.isPublic(m.getModifiers()) 17 && !Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0 18 && m.getReturnType() == URL.class) { 19 urlTypeIndex = i; 20 attribMethod = name; 21 // 结束for (int i = 0; i < pts.length; ++i)循环 22 break LBL_PTS; 23 } 24 } 25 } 26 ...暂时省略....
上面的代码是参数数组中不存在URL类型参数的情况。如果不存在URL类型的参数,就需要从所有的入参中判断,参数对象中是否可以通过get方法 获取到URL对象。如果不可以则抛出异常。
标签LBL_PTS用于结束最外部的循环。我们看到最外边的循环,还是参数数组pts。
第8行是拿到参数数组中单个参数的所有方法,再进行循环,判断是否存在满足如下条件: 1、方法名以get开头,或方法名大于3个字符;2、方法的访问权限是public; 3、非静态方法;4、方法参数数量为0; 5、方法返回值类型为URL的方法,如果存在赋值方法名给attribMethod ,跳出最外变循环。
我们接着往下看
1 ...暂时省略.... 2 // 如果参数中都不包含可返回的URL的get方法,抛出异常 3 if (attribMethod == null) { 4 throw new IllegalStateException("fail to create adaptive class for interface " + type.getName() 5 + ": not found url parameter or url attribute in parameters of method " 6 + method.getName()); 7 } 8 ...暂时省略....
我们看到,如果attribMethod 为空,也就是前面的两个循环没有找到存在返回URL方法的参数对象,直接抛出异常,方法结束执行。
如果attribMethod 不为空,即存在返回URL方法的参数对象,我们再往下看:
1 ...暂时省略.... 2 // Null point check 3 // 为可返回URL的参数生成判空代码,格式如下: 4 // if (arg + urlTypeIndex == null) 5 // throw new IllegalArgumentException("参数全限定名 + argument == null"); 6 String s = String.format( 7 "\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");", 8 urlTypeIndex, pts[urlTypeIndex].getName()); 9 code.append(s); 10 // 为 getter 方法返回的 URL 生成判空代码,格式如下: 11 // if (argN.getter方法名() == null) 12 // throw new IllegalArgumentException(参数全限定名 + argument getUrl() == null); 13 s = String.format( 14 "\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");", 15 urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod); 16 code.append(s); 17 // 生成赋值语句,格式如下: 18 // URL全限定名 url = argN.getter方法名(),比如 19 // com.alibaba.dubbo.common.URL url = invoker.getUrl(); 20 s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod); 21 code.append(s); 22 ...暂时省略....
第6行生成新类代码是空判断,如果参数数组下标为urlTypeIndex的参数为空,抛出异常。
第13行是返回URL类型方法的空判断,我们知道参数数组下标是urlTypeIndex的参数,存在返回URL类型的方法,方法名为attribMethod,此处就是判断方法attribMethod是否为空null。
第20行就是执行attribMethod,赋值给url变量。
至此,从入参中获得了URL类型的变量。前面是直接从参数数组中获取类型为URL的参数,后面是从参数数组中的某个可以返回URL参数方法的参数。两种方式目的就是获取到URL类型的变量,这个是必须的,因为自适应的扩展,获取扩展点的key是从URL中解析出来的。
在获取到URL类型的变量后,现在就要获取关键字key了,根据key从URL中获取的value,就是自适应扩展点在配置文本文件中对应的key。
我们接着往下看
1 ...暂时省略.... 2 // 获取方法@Adaptive的注解值 3 String[] value = adaptiveAnnotation.value(); 4 // value is not set, use the value generated from class name as the key 5 // @Adaptive的value为空,需要特殊处理 6 // 将类名转换为字符数组,然后遍历字符数组,并将字符存入StringBulder 7 // 若字符为大写字母,则向StringBuiilder中添加“.”,随后字符变为小写存入StringBuilder 8 // 比如LoadBalance经过处理,得到load.balance 9 if (value.length == 0) { 10 // 获取类名,并将类名转换为字符数组 11 char[] charArray = type.getSimpleName().toCharArray(); 12 StringBuilder sb = new StringBuilder(128); 13 // 遍历字符数组 14 for (int i = 0; i < charArray.length; i++) { 15 // 判断大小写 16 if (Character.isUpperCase(charArray[i])) { 17 if (i != 0) { 18 // 大写字符时,加 19 sb.append("."); 20 } 21 // 转为小写 22 sb.append(Character.toLowerCase(charArray[i])); 23 } else { 24 sb.append(charArray[i]); 25 } 26 } 27 value = new String[] { sb.toString() }; 28 } 29 ...暂时省略....
第3行,是直接从@Adaptive注解中获取value,类型为字符串数组。
如果@Adaptive注解没有设置value的值,接着看第9行的判断。
从第11行开始,自动生成从URL获取自适应扩展关键字的key。生成的逻辑是根据扩展点type的名称,遍历type名称的字符数组,除了首字符,遇到大写的字符前面加“.",大写转小写,组装的字符串就是要获取的value值。
我们接着往下看
1 ...暂时省略.... 2 // 检测方法列表中是否存在Invocation类型的参数 3 // 若存在,则为其生成判空代码和其他一些代码 4 boolean hasInvocation = false; 5 for (int i = 0; i < pts.length; ++i) { 6 // 判断参数名称是否等于 com.alibaba.dubbo.rpc.Invocation 7 if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) { 8 // Null Point check 9 // 为Invocation 类型参数生成判空代码 10 String s = String.format( 11 "\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i); 12 code.append(s); 13 // 生成getMethodName方法调用代码,格式为: 14 // String methodName = argN.getMethodName(); 15 s = String.format("\nString methodName = arg%d.getMethodName();", i); 16 code.append(s); 17 // 设置hasInvocation为true 18 hasInvocation = true; 19 break; 20 } 21 } 22 ...暂时省略....
这段代码选好pts,判断是否存在类型为Invocation的参数。如果存在生成为空判断,之后从Invocation类型的参数中获取methodName。并设置hasInvocation为true。
1 ...暂时省略.... 2 /** 3 * 根据SPI和Adaptive注解值生成“获取扩展名逻辑”,同时生成逻辑也受Invocation类型参数影响 生成格式如: String extName = 4 * url.getMethodParameter(methodName, "loadbalance","random"); 5 */ 6 // 设置默认扩展名,cachedDefaultName源于SPI注解值,默认情况下, 7 // SPI注解值为空串,此时cachedDefaultName 为 null 8 String defaultExtName = cachedDefaultName; 9 String getNameCode = null; 10 // 遍历value,value是Adaptive的注解值,上面有value的获取过程 11 // 此处循环的目的是生成从URL中获取扩展名的代码,生成的代码会赋值给getNameCode变量 12 // 这个循环的遍历顺序是由后向前遍历的 13 for (int i = value.length - 1; i >= 0; --i) { 14 // i为最后一个元素的坐标时 15 if (i == value.length - 1) { 16 // 默认扩展名非空 17 if (null != defaultExtName) { 18 // protocol是扩展名的一部分,可以通过getProtocol方法获取,其他则是从URL参数中获取 19 // 因为获取方式不同,因此要进行判断 20 if (!"protocol".equals(value[i])) { 21 // hasInvocation 用于标识方法参数列表中是否有Invocation类型参数 22 if (hasInvocation) { 23 // 生成的代码功能等价于下面的代码: 24 // url.getMethodParameter(methodName, value[i], defaultExtName) 25 // 以 LoadBalance 接口的 select 方法为例,最终生成的代码如下: 26 // url.getMethodParameter(methodName, "loadbalance", "random") 27 getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", 28 value[i], defaultExtName); 29 } else { 30 // 生成的代码功能等价于下面的代码: 31 // url.getParameter(value[i], defaultExtName) 32 getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], 33 defaultExtName); 34 } 35 } else { 36 // 生成的代码功能等价于下面的代码: 37 // ( url.getProtocol() == null ? defaultExtName : url.getProtocol() ) 38 getNameCode = String.format( 39 "( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName); 40 // 默认扩展名为空 41 } 42 } else { 43 if (!"protocol".equals(value[i])) { 44 if (hasInvocation) { 45 // 生成代码格式同上 46 getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", 47 value[i], defaultExtName); 48 } else { 49 // 生成的代码功能等价于下面的代码: 50 // url.getParameter(value[i]) 51 getNameCode = String.format("url.getParameter(\"%s\")", value[i]); 52 } 53 } else { 54 // 生成从 url 中获取协议的代码,比如 "dubbo" 55 getNameCode = "url.getProtocol()"; 56 } 57 } 58 } else { 59 if (!"protocol".equals(value[i])) { 60 if (hasInvocation) { 61 // 生成代码格式同上 62 getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", 63 value[i], defaultExtName); 64 } else { 65 // 生成的代码功能等价于下面的代码: 66 // url.getParameter(value[i], getNameCode) 67 // 以 Transporter 接口的 connect 方法为例,最终生成的代码如下: 68 // url.getParameter("client", url.getParameter("transporter", "netty")) 69 getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode); 70 } 71 } else { 72 // 生成的代码功能等价于下面的代码: 73 // url.getProtocol() == null ? getNameCode : url.getProtocol() 74 // 以 Protocol 接口的 connect 方法为例,最终生成的代码如下: 75 // url.getProtocol() == null ? "dubbo" : url.getProtocol() 76 getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", 77 getNameCode); 78 } 79 } 80 } 81 ...暂时省略....
第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后的代码
1 code.append("\nString extName = ").append(getNameCode).append(";"); 2 // check extName == null? 3 String s = String.format("\nif(extName == null) " 4 + "throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");", 5 type.getName(), Arrays.toString(value)); 6 code.append(s); 7 // 生成扩展获取代码,格式如下: 8 // type 全限定名 extension = (type全限定名)ExtensionLoader全限定名 9 // .getExtensionLoader(type全限定名.class).getExtension(extName); 10 // Tips: 格式化字符串中的 %11 s = String.format("\n%s extension = (%12 type.getName(), ExtensionLoader.class.getSimpleName(), type.getName()); 13 code.append(s); 14 15 // return statement 16 // 如果方法返回值类型非 void,则生成 return 语句。 17 if (!rt.equals(void.class)) { 18 code.append("\nreturn "); 19 } 20 21 s = String.format("extension.%s(", method.getName()); 22 code.append(s); 23 for (int i = 0; i < pts.length; i++) { 24 if (i != 0) 25 code.append(", "); 26 code.append("arg").append(i); 27 } 28 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方法
1 private Class> createAdaptiveExtensionClass() { 2 // 创建自适应扩展代码 字符串 3 String code = createAdaptiveExtensionClassCode(); 4 ClassLoader classLoader = findClassLoader(); 5 // 获取编译器实现类 6 com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader 7 .getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); 8 // 编译代码,获取自适应扩展类的Class 9 return compiler.compile(code, classLoader); 10 }
第3行就是我们前面分析的获取新类字符串的方法,拿到code之后,再获取类加载器,获取编辑器,执行编译。返回的就是自适应扩展类的Class对象。
通过此方法,再往上返回就是自适应扩展类的对象,以及缓存对象等逻辑。自适应扩展的获取基本就结束了。
四、总结
通过上面的分析,我们基本了解的自适应扩展点的实现逻辑,难点就是@Adaptive注解方法时,生成新类的字符串之处。别的逻辑还算清晰。如果在读到此处有困惑,请评论留言,我会进行详细解释。