直接展示一个具体的 Dubbo SPI
自适应拓展是什么样子,是一种非常好的表现其作用的方式。正如官方博客中所说的,它让人对自适应拓展有更加感性的认识,避免读者一开始就陷入复杂的代码生成逻辑。本文在此基础上,从更原始的使用方式上展现“动态加载”技术对“按需加载”的天然倾向,从更普遍的角度解释自适应拓展的本质目的,在介绍 Dubbo
的具体实现是如何约束自身从而规避缺点之后,详细梳理了 Dubbo SPI
自适应拓展的相关源码和工作原理。
站在现有设计回头看的视角更偏向于展现为什么这样设计很好,却并不好展现如果不这样设计会有什么问题,以至于有时候会有种这个设计很妙,但妙在哪里体会不够深的感觉。思考一项技术如何从最初发展到现在,解决以及试图解决哪些问题,因此可能引入哪些问题,也许脑补的并不完全符合历史事实,但仍然会让人更加深刻地认识这项技术本身,体会设计中的巧思,并避免一直陷在庞杂的细节处理中。
原理 在 Dubbo
中,很多拓展都是通过 SPI
机制动态加载的,比如 Protocol
、Cluster
和 LoadBalance
等。有些拓展我们并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。为了让大家对自适应拓展有一个感性的认识,下面我们通过一个实例进行演示。
示例 定义一个接口 Animal
。
1 2 3 public interface Animal { void bark () ; }
定义两个实现类 Dog
和 Cat
。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Dog implements Animal { @Override public void bark () { System.out.println("Dog bark..." ); } } public class Cat implements Animal { @Override public void bark () { System.out.println("Cat bark..." ); } }
在运行时根据参数动态地加载拓展。
1 2 3 4 5 6 7 8 9 public void bark (String type) { if (type == null ) { throw new IllegalArgumentException ("type == null" ); } Animal animal = ExtensionLoader.getExtensionLoader(Animal.class).getExtension(type); animal.bark(); }
改进 是不是感觉平平无奇?没错,当你拥有动态加载的能力后,按需加载是自然而然会产生的想法,并不是什么高大上的设计。两者甚至不仅仅是天性相合,可能更像是你中有我,我中有你。在正常场景中,这样一段代码也并不需要进一步被抽象和重构,它本身就很简洁。现在设想一下,你的应用中,有大量的拓展需要动态加载,你可能需要在很多地方写很多根据运行时参数动态加载拓展并调用方法的代码,就像下面这样:
1 2 3 4 5 6 7 8 9 10 Animal animal = ExtensionLoader.getExtensionLoader(Animal.class).getExtension(type);animal.bark(); WeelMaker weelMaker = ExtensionLoader.getExtensionLoader(WeelMaker.class).getExtension(weelMakerName);weelMaker.makeWeel(); LoadBalance loadBalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invocation.getLoadBalanceType());loadBalance.select();
这会带来一些小问题,总是需要写 ExtensionLoader.getExtensionLoader(XXX.class).getExtension(parameter)
这样重复的代码;引入了 ExtensionLoader
这个“中介”,不能直面拓展本身。后者可能有点难以体会,以动物园 Zoo
和 动物 Animal
举例。
在非动态加载情况下,我们可能会这样写:
1 2 3 4 5 6 7 8 9 10 11 public class Zoo { private List<Animal> animals; public void bark (String type) { for (Animal animal : animals) { if (type.equals(animal.name)) { animal.bark(); } } } }
在动态加载情况下,我们可能会这样写。在这种情况下,Zoo
没有合适的方式直接持有 Animal
,而是通过 ExtensionLoader
间接地持有。
1 2 3 4 5 6 7 8 public class Zoo { private ExtensionLoader<Animal> extensionLoader = ExtensionLoader.getExtensionLoader(Animal.class); public void bark (String type) { Animal animal = extensionLoader.getExtension(type); animal.bark(); } }
我们更想要以下这种直接持有 Animal
的方式,在运行时 animal
可以是 Dog
,也可以是 Cat
,还可以是其他的动物。
1 2 3 4 5 6 7 public class Zoo { private Animal animal; public void bark (String type) { animal.bark(); } }
Dubbo
采用了一种称为“自适应拓展”的巧妙设计,通过代理的方式,将动态加载拓展的代码整合到代理类(具体实现类)中。使用方调用代理对象,代理对象根据参数动态加载拓展并调用。例如 Animal
的自适应拓展,就像下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class AdaptiveAnimal implements Animal { public void bark (String type) { if (type == null ) { throw new IllegalArgumentException ("type == null" ); } Animal animal = ExtensionLoader.getExtensionLoader(Animal.class).getExtension(type); animal.bark(); } } Animal animal = new AdaptiveAnimal ();animal.bark(type);
当然,我们不希望需要手动地为每一个拓展编写 Adaptive
代理类,事实上,我们以往接触到的代理方案,大都是自动生成代理的,应该也不会有人会接受完全手写的方式。然而你可能会注意到一个不够和谐的缺点,bark
方法的参数列表中新增了 type
类型,这不太符合面向对象的设计原则。想象一个更奇怪的场景,我们要为一个方法引入与它本身格格不入的参数用于获取拓展。另外,我们可能需要通过一些标记或约定来告诉代理生成器,方法参数列表中哪一个参数是用于获取拓展的。事实上,Dubbo
的另一个设计规避了这一缺点,Dubbo
在公共契约 中提到:所有扩展点参数都包含 URL
参数,URL
作为上下文信息贯穿整个扩展点设计体系 。因此围绕着 Dubbo
以 URL
为中心的拓展体系,你很难设计出 Animal.bark(URL url)
这样不和谐的方法签名,也不用担心参数列表千奇百怪的情况。同时 Dubbo
并未完全抛弃手工编写自适应拓展的方式,而是予以保留。
手工编码的自适应拓展 在在 Dubbo
中,尽管很少但仍然存在手工编码的自适应拓展,这类拓展允许你不使用 URL
作为参数 ,查看它们的代码可以帮助我们更好地理解自适应拓展是如何在真实的应用场景中发挥作用的。以下是 ExtensionFactory
的自适应拓展,当你调用它的 getExtension
方法时,它就是将工作全权委托给 factory.getExtension(type, name)
完成的,而 factories
在创建 AdaptiveExtensionFactory
时就已经获取了。
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 @Adaptive public class AdaptiveExtensionFactory implements ExtensionFactory { private final List<ExtensionFactory> factories; public AdaptiveExtensionFactory () { ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class); List<ExtensionFactory> list = new ArrayList <ExtensionFactory>(); for (String name : loader.getSupportedExtensions()) { list.add(loader.getExtension(name)); } factories = Collections.unmodifiableList(list); } @Override public <T> T getExtension (Class<T> type, String name) { for (ExtensionFactory factory : factories) { T extension = factory.getExtension(type, name); if (extension != null ) { return extension; } } return null ; } }
至此,我们提到了按需加载是具备动态加载能力后自然的倾向,介绍了在拥有大量拓展情况下演变而来的自适应拓展设计,它的缺点和 Dubbo 是如何规避的。接下来,我们将进入源码分析部分。
源码分析 Adaptive 注解 Adaptive
注解是一个与自适应拓展息息相关的注解,该定义如下:
1 2 3 4 5 6 @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Adaptive { String[] value() default {}; }
根据 Target
注解的 value
可知,Adaptive
注解可标注在类或者方法上。当 Adaptive
注解标注在类上时,Dubbo
不会为该类生成代理类。当 Adaptive
注解标注在接口方法上时,Dubbo
则会为该方法生成代理逻辑。Adaptive
注解在类上的情况很少,在 Dubbo
中,仅有两个类被 Adaptive
注解标注,分别是 AdaptiveCompiler
和 AdaptiveExtensionFactory
。在这种情况下,拓展的加载逻辑由人工编码完成。在更多时候,Adaptive
注解是标注在接口方法上的,这表示拓展的加载逻辑需由框架自动生成。
获取自适应拓展 获取自适应拓展的入口方法是 getAdaptiveExtension
,使用 getOrCreate
的模式获取。
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 public T getAdaptiveExtension () { Object instance = cachedAdaptiveInstance.get(); if (instance == null ) { if (createAdaptiveInstanceError != null ) { throw new IllegalStateException ("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null ) { try { instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException ("Failed to create adaptive instance: " + t.toString(), t); } } } } return (T) instance; }
创建自适应拓展 当缓存为空时,就会通过 createAdaptiveExtension
方法创建。方法包含以下三个处理逻辑:
调用 getAdaptiveExtensionClass
方法获取自适应拓展的 Class
对象。
通过反射进行实例化。
调用 injectExtension
方法对拓展实例进行依赖注入。
手工编码的自适应拓展可能依赖其他拓展,但是框架生成的自适应拓展并不依赖其他拓展 。
1 2 3 4 5 6 7 private T createAdaptiveExtension () { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException ("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }
获取自适应拓展类 获取自适应拓展类的 getAdaptiveExtensionClass
方法包含以下三个处理逻辑:
通过 getExtensionClasses
方法获取所有拓展类。
检查缓存 cachedAdaptiveClass
,如果不为 null
,则返回缓存。
如果缓存为 null
,则调用 createAdaptiveExtensionClass
创建自适应拓展类(代理类)。
在Dubbo SPI 的工作原理 中我们分析过 getExtensionClasses
方法,在获取拓展的所有实现类时,如果某个实现类被 Adaptive
注解标注了,那么该类就会被赋值给 cachedAdaptiveClass
变量。“原理”部分介绍的 AdaptiveExtensionFactory
就属于这种情况,我们不再细谈。按前文所说,在绝大多数情况下,Adaptive
注解都是用于标注方法而非标注具体的实现类,因此在大多数情况下程序都会走第三个步骤,由框架自动生成自适应拓展类(代理类)。
1 2 3 4 5 6 7 private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null ) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
到目前为止,获取自适应拓展的过程和获取普通拓展的过程是非常相似的 ,使用 getOrCreate
的模式获取拓展,如果缓存为空则创建,创建的时候会先加载全部的拓展实现类,从中获取目标类,通过反射进行实例化,最后进行依赖注入。区别在于获取目标类时,在自适应拓展情况下,返回的可能是一个生成的代理类。生成的过程非常复杂,是我们接下来关注的重点。
生成自适应拓展类 生成自适应拓展类的方式相比于以往接触的生成代理类的方式更加“直观且容易理解”,但是相应的,拼接字符串部分的代码并不容易阅读。
通过拼接字符串得到代理类的源码。
使用编译器编译得到 Class
对象。
在新版本中,这部分代码的可读性有了非常大的提升,原先冗长的处理逻辑被抽象为多个命名含义清晰的方法。
1 2 3 4 5 6 7 8 9 10 private Class<?> createAdaptiveExtensionClass() { String code = new AdaptiveClassCodeGenerator (type, cachedDefaultName).generate(); ClassLoader classLoader = findClassLoader(); org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader); }
为了更直观地了解代码生成的效果及其实现的功能,以 Protocol
为例,生成的完整代码(已经经过格式化)展示如下 。
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 package org.apache.dubbo.rpc;import org.apache.dubbo.common.extension.ExtensionLoader;public class Protocol$Adaptive implements org .apache.dubbo.rpc.Protocol { public void destroy () { throw new UnsupportedOperationException ( "The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!" ); } public int getDefaultPort () { throw new UnsupportedOperationException ( "The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!" ); } public org.apache.dubbo.rpc.Invoker refer (java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException { if (arg1 == null ) throw new IllegalArgumentException ("url == null" ); org.apache.dubbo.common.URL url = arg1; String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null ) throw new IllegalStateException ("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])" ); org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader .getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } public java.util.List getServers () { throw new UnsupportedOperationException ( "The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!" ); } public org.apache.dubbo.rpc.Exporter export (org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException { if (arg0 == null ) throw new IllegalArgumentException ("org.apache.dubbo.rpc.Invoker argument == null" ); if (arg0.getUrl() == null ) throw new IllegalArgumentException ("org.apache.dubbo.rpc.Invoker argument getUrl() == null" ); org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null ) throw new IllegalStateException ("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])" ); org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader .getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); } }
生成的代理类需完成以下功能:
非 adaptive
方法,直接抛出异常。
adaptive
方法:
准备工作:在参数判空校验之后,从中获取到 URL
对象,结合 URL
对象和默认拓展名得到最终的拓展名 extName
。
核心功能:先获取拓展的 ExtensionLoader
,再根据拓展名 extName
获取拓展,最后调用拓展的同名方法。
以上的功能在表面上看来并不复杂,事实上,想要实现的目标处理逻辑也并不复杂,只在为了提供足够的可扩展性,具体实现变得很复杂。复杂的处理逻辑主要集中在如何为“准备工作”部分生成相应的代码,大概可以总结为:在获取拓展前,Dubbo
会直接或间接地从参数列表中查找 URL
对象,所谓直接就是 URL
对象直接在参数列表中,所谓间接就是 URL
对象是其中一个参数的属性。在得到 URL
对象后,Dubbo
会尝试以 Adaptive
注解的 value
为 key
,从 URL
中获取值作为拓展名,如果获取不到则使用默认拓展名 defaultExtName
。实际的实现更加复杂,需要耐心阅读和测试。
自适应拓展类代码生成器 新版本将代码生成的逻辑抽象到自适应拓展类代码生成器中,注意参数只有 type
和 defaultExtName
,从这里也可以看出如何确定最终加载的拓展,取决于这两个参数和被调用方法的入参。
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 public AdaptiveClassCodeGenerator (Class<?> type, String defaultExtName) { this .type = type; this .defaultExtName = defaultExtName; } public String generate () { if (!hasAdaptiveMethod()) { throw new IllegalStateException ("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!" ); } StringBuilder code = new StringBuilder (); code.append(generatePackageInfo()); code.append(generateImports()); code.append(generateClassDeclaration()); Method[] methods = type.getMethods(); for (Method method : methods) { code.append(generateMethod(method)); } code.append("}" ); if (logger.isDebugEnabled()) { logger.debug(code.toString()); } return code.toString(); }
检测 Adaptive 注解 在生成代理类源码之前,generate
方法会先通过反射检测接口方法中是否至少有一个标注了 Adaptive
注解,若不满足,就会抛出异常。
流式编程使用得当的话很有可读性啊。
1 2 3 private boolean hasAdaptiveMethod () { return Arrays.stream(type.getMethods()).anyMatch(m -> m.isAnnotationPresent(Adaptive.class)); }
生成类 生成代理类源码的顺序和普通 Java
类文件中内容的顺序一致:
先忽略“生成方法”的部分,以 Dubbo
的 Protocol
拓展为例,生成的代码如下:
1 2 3 4 5 package org.apache.dubbo.rpc;import org.apache.dubbo.common.extension.ExtensionLoader;public class Protocol$Adaptive implements org .apache.dubbo.rpc.Protocol { }
生成方法 生成方法的过程同样被抽象为几个命名含义清晰的方法,包含以下五个部分:
返回值
方法名
方法内容
方法参数
方法抛出的异常
1 2 3 4 5 6 7 8 private String generateMethod (Method method) { String methodReturnType = method.getReturnType().getCanonicalName(); String methodName = method.getName(); String methodContent = generateMethodContent(method); String methodArgs = generateMethodArguments(method); String methodThrows = generateMethodThrows(method); return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent); }
除了最重要的“方法内容”部分,其他部分都是复制原方法的信息,并不复杂。生成“方法内容”部分,分为是否被 Adaptive
注解标注。
1 2 3 4 5 6 7 8 9 10 11 private String generateMethodContent (Method method) { Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder (512 ); if (adaptiveAnnotation == null ) { return generateUnsupported(method); } else { } return code.toString(); }
无 Adaptive 注解标注的方法 对于无 Adaptive
注解标注的方法,生成逻辑很简单,就是生成抛出异常的代码。
1 2 3 private String generateUnsupported (Method method) { return String.format(CODE_UNSUPPORTED, method, type.getName()); }
以 Protocol
接口的 destroy
方法为例,生成的内容如下 :
1 throw new UnsupportedOperationException ("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!" );
有 Adaptive 注解标注的方法 对于有 Adaptive 注解标注的方法,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int urlTypeIndex = getUrlTypeIndex(method);if (urlTypeIndex != -1 ) { code.append(generateUrlNullCheck(urlTypeIndex)); } else { code.append(generateUrlAssignmentIndirectly(method)); } String[] value = getMethodAdaptiveValue(adaptiveAnnotation); boolean hasInvocation = hasInvocationArgument(method);code.append(generateInvocationArgumentNullCheck(method)); code.append(generateExtNameAssignment(value, hasInvocation)); code.append(generateExtNameNullCheck(value)); code.append(generateExtensionAssignment()); code.append(generateReturnAndInvocation(method));
查找 URL 类型的参数 直接 从方法的参数类型列表中查找第一个 URL
类型的参数,返回其索引。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private int getUrlTypeIndex (Method method) { int urlTypeIndex = -1 ; Class<?>[] pts = method.getParameterTypes(); for (int i = 0 ; i < pts.length; ++i) { if (pts[i].equals(URL.class)) { urlTypeIndex = i; break ; } } return urlTypeIndex; } private String generateUrlNullCheck (int index) { return String.format(CODE_URL_NULL_CHECK, index, URL.class.getName(), index); }
间接 从方法的参数类型列表中,查找 URL
类型的参数,并生成判空检查和赋值代码。
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 private String generateUrlAssignmentIndirectly (Method method) { Class<?>[] pts = method.getParameterTypes(); Map<String, Integer> getterReturnUrl = new HashMap <>(); for (int i = 0 ; i < pts.length; ++i) { for (Method m : pts[i].getMethods()) { String name = m.getName(); if ((name.startsWith("get" ) || name.length() > 3 ) && Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0 && m.getReturnType() == URL.class) { getterReturnUrl.put(name, i); } } } if (getterReturnUrl.size() <= 0 ) { throw new IllegalStateException ("Failed to create adaptive class for interface " + type.getName() + ": not found url parameter or url attribute in parameters of method " + method.getName()); } Integer index = getterReturnUrl.get("getUrl" ); if (index != null ) { return generateGetUrlNullCheck(index, pts[index], "getUrl" ); } else { Map.Entry<String, Integer> entry = getterReturnUrl.entrySet().iterator().next(); return generateGetUrlNullCheck(entry.getValue(), pts[entry.getValue()], entry.getKey()); } } private String generateGetUrlNullCheck (int index, Class<?> type, String method) { StringBuilder code = new StringBuilder (); code.append(String.format("if (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");\n" , index, type.getName())); code.append(String.format("if (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");\n" , index, method, type.getName(), method)); code.append(String.format("%s url = arg%d.%s();\n" , URL.class.getName(), index, method)); return code.toString(); }
以 Protocol
的 refer
和 export
方法为例,生成的内容如下 :
1 2 3 4 5 6 7 if (arg1 == null ) throw new IllegalArgumentException ("url == null" );org.apache.dubbo.common.URL url = arg1; if (arg0 == null ) throw new IllegalArgumentException ("org.apache.dubbo.rpc.Invoker argument == null" );if (arg0.getUrl() == null ) throw new IllegalArgumentException ("org.apache.dubbo.rpc.Invoker argument getUrl() == null" );org.apache.dubbo.common.URL url = arg0.getUrl();
获取 Adaptive 注解的 value 1 2 3 4 5 6 7 8 9 10 private String[] getMethodAdaptiveValue(Adaptive adaptiveAnnotation) { String[] value = adaptiveAnnotation.value(); if (value.length == 0 ) { String splitName = StringUtils.camelToSplitName(type.getSimpleName(), "." ); value = new String []{splitName}; } return value; }
检测 Invocation 类型的参数 检测是否有 Invocation
类型的参数,并生成判空检查代码和赋值代码。从 Invocation
可以获得 methodName
。
1 2 3 4 5 6 7 8 9 10 11 private boolean hasInvocationArgument (Method method) { Class<?>[] pts = method.getParameterTypes(); return Arrays.stream(pts).anyMatch(p -> CLASSNAME_INVOCATION.equals(p.getName())); } private String generateInvocationArgumentNullCheck (Method method) { Class<?>[] pts = method.getParameterTypes(); return IntStream.range(0 , pts.length).filter(i -> CLASSNAME_INVOCATION.equals(pts[i].getName())) .mapToObj(i -> String.format(CODE_INVOCATION_ARGUMENT_NULL_CHECK, i, i)) .findFirst().orElse("" ); }
以 LoadBalance
的 select
方法为例,生成的内容如下:
1 2 if (arg2 == null ) throw new IllegalArgumentException ("invocation == null" );String methodName = arg2.getMethodName();
获取拓展名 本方法用于根据 SPI
和 Adaptive
注解的 value
生成“获取拓展名”的代码,同时生成逻辑还受 Invocation
影响,因此相对复杂。总结的规则如下:
正常情况下,使用 url.getParameter(value[i]) 获取
如果默认拓展名非空,使用 url.getParameter(value[i], defaultExtName) 获取
如果存在 Invocation,不论默认拓展名是否为空,总是使用 url.getMethodParameter(methodName, value[i], defaultExtName) 获取
因为 protocol 是 url 的一部分,所以可以直接通过 getProtocol 获取。是否使用默认拓展名的方式就退化为原始的三元表达式。
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 private String generateExtNameAssignment (String[] value, boolean hasInvocation) { 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); } } } return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode); }
加载拓展 1 2 3 private String generateExtensionAssignment () { return String.format(CODE_EXTENSION_ASSIGNMENT, type.getName(), ExtensionLoader.class.getSimpleName(), type.getName()); }
以 Protocol
接口的 refer
方法为例,生成的内容如下:
1 org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
调用与返回 生成方法调用语句,如有必要,返回结果。
1 2 3 4 5 6 7 8 9 private String generateReturnAndInvocation (Method method) { String returnStatement = method.getReturnType().equals(void .class) ? "" : "return " ; String args = IntStream.range(0 , method.getParameters().length) .mapToObj(i -> String.format(CODE_EXTENSION_METHOD_INVOKE_ARGUMENT, i)) .collect(Collectors.joining(", " )); return returnStatement + String.format("extension.%s(%s);\n" , method.getName(), args); }
以 Protocol
接口的 refer
方法为例,生成的内容如下:
1 return extension.refer(arg0, arg1);
新版本通过提炼方法、使用流式编程和使用 String.format()
代替 StringBuilder,提供了更好的代码可读性。官方写得源码解析真好。
参考文章