引言 前文中,我们已经介绍 Dubbo SPI 和 Java SPI 的区别以及一些增强,本文我们着重介绍 Dubbo SPI 的实现方式,其他 Dubbo 相关文章均收录于 <Dubbo系列文章> 。
实现原理 SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。
我们首先通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。这其中,getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则创建一个新的实例。该方法的逻辑比较简单,本章就不进行分析了。下面我们从 ExtensionLoader 的 getExtension 方法作为入口,对拓展类对象的获取过程进行详细的分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public T getExtension (String name) { if (name == null || name.length() == 0 ) throw new IllegalArgumentException("Extension name == null" ); if ("true" .equals(name)) { return getDefaultExtension(); } Holder<Object> holder = cachedInstances.get(name); if (holder == null ) { cachedInstances.putIfAbsent(name, new Holder<Object>()); holder = cachedInstances.get(name); } Object instance = holder.get(); if (instance == null ) { synchronized (holder) { instance = holder.get(); if (instance == null ) { instance = createExtension(name); holder.set(instance); } } } return (T) instance; }
上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。下面我们来看一下创建拓展对象的过程是怎样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 private T createExtension (String name) { Class<?> clazz = getExtensionClasses().get(name); if (clazz == null ) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null ) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension( (T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("..." ); } }
createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:
通过 getExtensionClasses 获取所有的拓展类
通过反射创建拓展对象
向拓展对象中注入依赖
将拓展对象包裹在相应的 Wrapper 对象中
以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。在接下来的章节中,将会重点分析 getExtensionClasses 方法的逻辑,以及简单介绍 Dubbo IOC 的具体实现。
获取所有的拓展类 我们在通过名称获取拓展类之前,首先需要根据配置文件解析出拓展项名称到拓展类的映射关系表(Map<名称, 拓展类>),之后再根据拓展项名称从映射关系表中取出相应的拓展类即可。相关过程的代码分析如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; }
这里也是先检查缓存,若缓存未命中,则通过 synchronized 加锁。加锁后再次检查缓存,并判空。此时如果 classes 仍为 null,则通过 loadExtensionClasses 加载拓展类。下面分析 loadExtensionClasses 方法的逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 private Map<String, Class<?>> loadExtensionClasses() { final SPI defaultAnnotation = type.getAnnotation(SPI.class); if (defaultAnnotation != null ) { String value = defaultAnnotation.value(); if ((value = value.trim()).length() > 0 ) { String[] names = NAME_SEPARATOR.split(value); if (names.length > 1 ) { throw new IllegalStateException("more than 1 default extension name on extension..." ); } if (names.length == 1 ) { cachedDefaultName = names[0 ]; } } } Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadDirectory(extensionClasses, DUBBO_DIRECTORY); loadDirectory(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; }
loadExtensionClasses 方法总共做了两件事情,一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件。SPI 注解解析过程比较简单,无需多说。下面我们来看一下 loadDirectory 做了哪些事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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("..." ); } }
loadDirectory 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource 方法加载资源。我们继续跟下去,看一下 loadResource 方法的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 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 = 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..." ); } } } } finally { reader.close(); } } catch (Throwable t) { logger.error("Exception when load extension class..." ); } }
loadResource 方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass 方法进行其他操作。loadClass 方法用于主要用于操作缓存,该方法的逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 private void loadClass (Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("..." ); } if (clazz.isAnnotationPresent(Adaptive.class)) { if (cachedAdaptiveClass == null ) { cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("..." ); } } else if (isWrapperClass(clazz)) { Set<Class<?>> wrappers = cachedWrapperClasses; if (wrappers == null ) { cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); wrappers = cachedWrapperClasses; } wrappers.add(clazz); } else { clazz.getConstructor(); if (name == null || name.length() == 0 ) { name = findAnnotationName(clazz); if (name.length() == 0 ) { throw new IllegalStateException("..." ); } } String[] names = NAME_SEPARATOR.split(name); if (names != null && names.length > 0 ) { 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); } Class<?> c = extensionClasses.get(n); if (c == null ) { extensionClasses.put(n, clazz); } else if (c != clazz) { throw new IllegalStateException("..." ); } } } } }
如上,loadClass 方法操作了不同的缓存,比如 cachedAdaptiveClass、cachedWrapperClasses 和 cachedNames 等等。除此之外,该方法没有其他什么逻辑了。
到此,关于缓存类加载的过程就分析完了。整个过程没什么特别复杂的地方,大家按部就班的分析即可,不懂的地方可以调试一下。接下来,我们来聊聊 Dubbo IOC 方面的内容。
Dubbo IOC Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中。整个过程对应的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private T injectExtension (T instance) { try { if (objectFactory != null ) { for (Method method : instance.getClass().getMethods()) { if (method.getName().startsWith("set" ) && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { Class<?> pt = method.getParameterTypes()[0 ]; try { String property = method.getName().length() > 3 ? method.getName().substring(3 , 4 ).toLowerCase() + method.getName().substring(4 ) : "" ; Object object = objectFactory.getExtension(pt, property); if (object != null ) { method.invoke(instance, object); } } catch (Exception e) { logger.error("fail to inject via method..." ); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; }
在上面代码中,objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展。
Dubbo IOC 目前仅支持 setter 方式注入,总的来说,逻辑比较简单易懂。
Dubbo 自适应机制 在 Dubbo 中,很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。这听起来有些矛盾。拓展未被加载,那么拓展方法就无法被调用(静态方法除外)。拓展方法未被调用,拓展就无法被加载。对于这个矛盾的问题,Dubbo 通过自适应拓展机制很好的解决了。自适应拓展机制的实现逻辑比较复杂,首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过 javassist 或 jdk 编译这段代码,得到 Class 类。最后再通过反射创建代理类,整个过程比较复杂。为了让大家对自适应拓展有一个感性的认识,下面我们通过一个示例进行演示。这是一个与汽车相关的例子,我们有一个车轮制造厂接口 WheelMaker:
在对自适应拓展生成过程进行深入分析之前,我们先来看一下与自适应拓展息息相关的一个注解,即 Adaptive 注解。该注解的定义如下:
1 2 3 4 5 6 @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Adaptive { String[] value() default {}; }
从上面的代码中可知,Adaptive 可注解在类或方法上。当 Adaptive 注解在类上时,Dubbo 不会为该类生成代理类。注解在方法(接口方法)上时,Dubbo 则会为该方法生成代理逻辑。Adaptive 注解在类上的情况很少,在 Dubbo 中,仅有两个类被 Adaptive 注解了,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。更多时候,Adaptive 是注解在接口方法上的,表示拓展的加载逻辑需由框架自动生成。Adaptive 注解的地方不同,相应的处理逻辑也是不同的。注解在类上时,处理逻辑比较简单,会直接作为自适应类。注解在接口方法上时,处理逻辑较为复杂,接下来将会重点分析此块逻辑。
获取自适应拓展 getAdaptiveExtension 方法是获取自适应拓展的入口方法,因此下面我们从这个方法进行分析。相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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: ..." ); } } } } else { throw new IllegalStateException("fail to create adaptive instance: ..." ); } } return (T) instance; }
getAdaptiveExtension 方法首先会检查缓存,缓存未命中,则调用 createAdaptiveExtension 方法创建自适应拓展。下面,我们看一下 createAdaptiveExtension 方法的代码。
1 2 3 4 5 6 7 8 private T createAdaptiveExtension () { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can not create adaptive extension ..." ); } }
createAdaptiveExtension 方法的代码比较少,但却包含了三个逻辑,分别如下:
调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象
通过反射进行实例化
调用 injectExtension 方法向拓展实例中注入依赖
前两个逻辑比较好理解,第三个逻辑用于向自适应拓展对象中注入依赖。这个逻辑看似多余,但有存在的必要,这里简单说明一下。前面说过,Dubbo 中有两种类型的自适应拓展,一种是手工编码的,一种是自动生成的。手工编码的自适应拓展中可能存在着一些依赖,而自动生成的 Adaptive 拓展则不会依赖其他类。这里调用 injectExtension 方法的目的是为手工编码的自适应拓展注入依赖,这一点需要大家注意一下。关于 injectExtension 方法,前文已经分析过了,这里不再赘述。接下来,分析 getAdaptiveExtensionClass 方法的逻辑。
1 2 3 4 5 6 7 8 9 10 private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null ) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
getAdaptiveExtensionClass 方法同样包含了三个逻辑,如下:
调用 getExtensionClasses 获取所有的拓展类
检查缓存,若缓存不为空,则返回缓存
若缓存为空,则调用 createAdaptiveExtensionClass 创建自适应拓展类
这三个逻辑看起来平淡无奇,似乎没有多讲的必要。但是这些平淡无奇的代码中隐藏了着一些细节,需要说明一下。首先从第一个逻辑说起,getExtensionClasses 这个方法用于获取某个接口的所有实现类。比如该方法可以获取 Protocol 接口的 DubboProtocol、HttpProtocol、InjvmProtocol 等实现类。在获取实现类的过程中,如果某个某个实现类被 Adaptive 注解修饰了,那么该类就会被赋值给 cachedAdaptiveClass 变量。此时,上面步骤中的第二步条件成立(缓存不为空),直接返回 cachedAdaptiveClass 即可。如果所有的实现类均未被 Adaptive 注解修饰,那么执行第三步逻辑,创建自适应拓展类。相关代码如下:
1 2 3 4 5 6 7 8 9 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(); return compiler.compile(code, classLoader); }
createAdaptiveExtensionClass 方法用于生成自适应拓展类,该方法首先会生成自适应拓展类的源码,然后通过 Compiler 实例(Dubbo 默认使用 javassist 作为编译器)编译源码,得到代理类 Class 实例。接下来,我们把重点放在代理类代码生成的逻辑上,其他逻辑大家自行分析。
拓展类代码生成 在生成代理类源码之前,createAdaptiveExtensionClassCode 方法首先会通过反射检测接口方法是否包含 Adaptive 注解。对于要生成自适应拓展的接口,Dubbo 要求该接口至少有一个方法被 Adaptive 注解修饰。若不满足此条件,就会抛出运行时异常。相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Method[] methods = type.getMethods(); boolean hasAdaptiveAnnotation = false ;for (Method m : methods) { if (m.isAnnotationPresent(Adaptive.class)) { hasAdaptiveAnnotation = true ; break ; } } if (!hasAdaptiveAnnotation) throw new IllegalStateException("No adaptive method on extension ..." );
通过 Adaptive 注解检测后,即可开始生成代码。代码生成的顺序与 Java 文件内容顺序一致,首先会生成 package 语句,然后生成 import 语句,紧接着生成类名等代码。整个逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 codeBuilder.append("package " ).append(type.getPackage().getName()).append(";" ); codeBuilder.append("\nimport " ).append(ExtensionLoader.class.getName()).append(";"); codeBuilder.append("\npublic class " ) .append(type.getSimpleName()) .append("$Adaptive" ) .append(" implements " ) .append(type.getCanonicalName()) .append(" {" ); codeBuilder.append("\n}" );
这里使用 ${…} 占位符代表其他代码的生成逻辑,该部分逻辑将在随后进行分析。上面代码不是很难理解,下面直接通过一个例子展示该段代码所生成的内容。以 Dubbo 的 Protocol 接口为例,生成的代码如下:
1 2 3 4 5 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 注解方法”,下面我们先来看看此种方法的代码生成逻辑是怎样的。
对于接口方法,我们可以按照需求标注 Adaptive 注解。以 Protocol 接口为例,该接口的 destroy 和 getDefaultPort 未标注 Adaptive 注解,其他方法均标注了 Adaptive 注解。Dubbo 不会为没有标注 Adaptive 注解的方法生成代理逻辑,对于该种类型的方法,仅会生成一句抛出异常的代码。生成逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 for (Method method : methods) { Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512 ); if (adaptiveAnnotation == null ) { code.append("throw new UnsupportedOperationException(\"method " ) .append(method.toString()).append(" of interface " ) .append(type.getName()).append(" is not adaptive method!\");" ); } else { } }
以 Protocol 接口的 destroy 方法为例,上面代码生成的内容如下:
1 2 throw new UnsupportedOperationException( "method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!" );
前面说过方法代理逻辑会从 URL 中提取目标拓展的名称,因此代码生成逻辑的一个重要的任务是从方法的参数列表或者其他参数中获取 URL 数据。举例说明一下,我们要为 Protocol 接口的 refer 和 export 方法生成代理逻辑。在运行时,通过反射得到的方法定义大致如下:
1 2 Invoker refer (Class<T> arg0, URL arg1) throws RpcException ;Exporter export (Invoker<T> arg0) throws RpcException ;
对于 refer 方法,通过遍历 refer 的参数列表即可获取 URL 数据,这个还比较简单。对于 export 方法,获取 URL 数据则要麻烦一些。export 参数列表中没有 URL 参数,因此需要从 Invoker 参数中获取 URL 数据。获取方式是调用 Invoker 中可返回 URL 的 getter 方法,比如 getUrl。如果 Invoker 中无相关 getter 方法,此时则会抛出异常。整个逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 for (Method method : methods) { Class<?> rt = method.getReturnType(); Class<?>[] pts = method.getParameterTypes(); Class<?>[] ets = method.getExceptionTypes(); Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512 ); if (adaptiveAnnotation == null ) { } else { int urlTypeIndex = -1 ; for (int i = 0 ; i < pts.length; ++i) { if (pts[i].equals(URL.class)) { urlTypeIndex = i; break ; } } if (urlTypeIndex != -1 ) { String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");" , urlTypeIndex); code.append(s); s = String.format("\n%s url = arg%d;" , URL.class.getName(), urlTypeIndex); code.append(s); } else { String attribMethod = null ; LBL_PTS: for (int i = 0 ; i < pts.length; ++i) { Method[] ms = pts[i].getMethods(); for (Method m : ms) { String name = m.getName(); 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; break LBL_PTS; } } } if (attribMethod == null ) { throw new IllegalStateException("fail to create adaptive class for interface ..." ); } String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");" , urlTypeIndex, pts[urlTypeIndex].getName()); code.append(s); s = String.format("\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");" , urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod); code.append(s); s = String.format("%s url = arg%d.%s();" , URL.class.getName(), urlTypeIndex, attribMethod); code.append(s); } } }
上面代码有点多,需要耐心看一下。这段代码主要目的是为了获取 URL 数据,并为之生成判空和赋值代码。以 Protocol 的 refer 和 export 方法为例,上面的代码为它们生成如下内容(代码已格式化):
1 2 3 4 5 6 7 8 9 10 11 if (arg1 == null ) throw new IllegalArgumentException("url == null" ); com.alibaba.dubbo.common.URL url = arg1; if (arg0 == null ) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null" ); if (arg0.getUrl() == null ) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null" ); com.alibaba.dubbo.common.URL url = arg0.getUrl();
Adaptive 注解值 value 类型为 String[],可填写多个值,默认情况下为空数组。若 value 为非空数组,直接获取数组内容即可。若 value 为空数组,则需进行额外处理。处理过程是将类名转换为字符数组,然后遍历字符数组,并将字符放入 StringBuilder 中。若字符为大写字母,则向 StringBuilder 中添加点号,随后将字符变为小写存入 StringBuilder 中。比如 LoadBalance 经过处理后,得到 load.balance。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 for (Method method : methods) { Class<?> rt = method.getReturnType(); Class<?>[] pts = method.getParameterTypes(); Class<?>[] ets = method.getExceptionTypes(); Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512 ); if (adaptiveAnnotation == null ) { } else { String[] value = adaptiveAnnotation.value(); 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()}; } } }
此段逻辑是检测方法列表中是否存在 Invocation 类型的参数,若存在,则为其生成判空代码和其他一些代码。相应的逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 for (Method method : methods) { Class<?> rt = method.getReturnType(); Class<?>[] pts = method.getParameterTypes(); Class<?>[] ets = method.getExceptionTypes(); Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512 ); if (adaptiveAnnotation == null ) { } else { boolean hasInvocation = false ; for (int i = 0 ; i < pts.length; ++i) { if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation" )) { String s = String.format("\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");" , i); code.append(s); s = String.format("\nString methodName = arg%d.getMethodName();" , i); code.append(s); hasInvocation = true ; break ; } } } }
本段逻辑用于根据 SPI 和 Adaptive 注解值生成“获取拓展名逻辑”,同时生成逻辑也受 Invocation 类型参数影响,综合因素导致本段逻辑相对复杂。本段逻辑可以会生成但不限于下面的代码:
1 2 3 4 5 String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); String extName = url.getMethodParameter(methodName, "loadbalance" , "random" ); String extName = url.getParameter("client" , url.getParameter("transporter" , "netty" ));
本段逻辑复杂之处在于条件分支比较多,大家在阅读源码时需要知道每个条件分支的意义是什么,否则不太容易看懂相关代码。下面开始分析本段逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 for (Method method : methods) { Class<?> rt = method.getReturnType(); Class<?>[] pts = method.getParameterTypes(); Class<?>[] ets = method.getExceptionTypes(); Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512 ); if (adaptiveAnnotation == null ) { } else { String defaultExtName = cachedDefaultName; String getNameCode = null ; for (int i = value.length - 1 ; i >= 0 ; --i) { if (i == value.length - 1 ) { if (null != defaultExtName) { if (!"protocol" .equals(value[i])) if (hasInvocation) getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")" , value[i], defaultExtName); else getNameCode = String.format("url.getParameter(\"%s\", \"%s\")" , value[i], defaultExtName); else 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 getNameCode = String.format("url.getParameter(\"%s\")" , value[i]); else getNameCode = "url.getProtocol()" ; } } else { if (!"protocol" .equals(value[i])) if (hasInvocation) getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")" , value[i], defaultExtName); else getNameCode = String.format("url.getParameter(\"%s\", %s)" , value[i], getNameCode); else getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()" , getNameCode); } } code.append("\nString extName = " ).append(getNameCode).append(";" ); 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); } }
这里我以 Transporter 接口的自适应拓展类代码生成过程举例说明。首先看一下 Transporter 接口的定义,如下:
1 2 3 4 5 6 7 8 9 10 @SPI("netty") public interface Transporter { @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY}) Server bind (URL url, ChannelHandler handler) throws RemotingException ; @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) Client connect (URL url, ChannelHandler handler) throws RemotingException ; }
下面对 connect 方法代理逻辑生成的过程进行分析,此时生成代理逻辑所用到的变量如下:
1 2 3 4 String defaultExtName = "netty" ; boolean hasInvocation = false ;String getNameCode = null ; String[] value = ["client" , "transporter" ];
下面对 value 数组进行遍历,此时 i = 1, value[i] = “transporter”,生成的代码如下:
1 getNameCode = url.getParameter("transporter" , "netty" );
接下来,for 循环继续执行,此时 i = 0, value[i] = “client”,生成的代码如下:
1 getNameCode = url.getParameter("client" , url.getParameter("transporter" , "netty" ));
for 循环结束运行,现在为 extName 变量生成赋值和判空代码,如下:
1 2 3 4 5 6 String extName = url.getParameter("client" , url.getParameter("transporter" , "netty" )); if (extName == null ) { throw new IllegalStateException( "Fail to get extension(com.alibaba.dubbo.remoting.Transporter) name from url(" + url.toString() + ") use keys([client, transporter])" ); }
本段代码逻辑用于根据拓展名加载拓展实例,并调用拓展实例的目标方法。相关逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 for (Method method : methods) { Class<?> rt = method.getReturnType(); Class<?>[] pts = method.getParameterTypes(); Class<?>[] ets = method.getExceptionTypes(); Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512 ); if (adaptiveAnnotation == null ) { } else { s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);" , type.getName(), ExtensionLoader.class.getSimpleName(), type.getName()); code.append(s); 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(");" ); } }
以 Protocol 接口举例说明,上面代码生成的内容如下:
1 2 3 com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader .getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1);
本节进行代码生成的收尾工作,主要用于生成方法定义的代码。相关逻辑如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 for (Method method : methods) { Class<?> rt = method.getReturnType(); Class<?>[] pts = method.getParameterTypes(); Class<?>[] ets = method.getExceptionTypes(); Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512 ); if (adaptiveAnnotation == null ) { } else { } } codeBuilder.append("\npublic " ) .append(rt.getCanonicalName()) .append(" " ) .append(method.getName()) .append("(" ); for (int i = 0 ; i < pts.length; i++) { if (i > 0 ) { codeBuilder.append(", " ); } codeBuilder.append(pts[i].getCanonicalName()); codeBuilder.append(" " ); codeBuilder.append("arg" ).append(i); } codeBuilder.append(")" ); if (ets.length > 0 ) { codeBuilder.append(" throws " ); for (int i = 0 ; i < ets.length; i++) { if (i > 0 ) { codeBuilder.append(", " ); } codeBuilder.append(ets[i].getCanonicalName()); } } codeBuilder.append(" {" ); codeBuilder.append(code.toString()); codeBuilder.append("\n}" );
以 Protocol 的 refer 方法为例,上面代码生成的内容如下:
1 2 3 public com.alibaba.dubbo.rpc.Invoker refer (java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) { }
到此,关于自适应拓展的原理,实现就分析完了。总的来说自适应拓展整个逻辑还是很复杂的,并不是很容易弄懂。
参考内容 [1]《深入理解Apache Dubbo与实战》 [2] dubbo 官方文档