Dubbo架构篇 - 自适应拓展
前言
关于自适应拓展机制,这里引用一下Dubbo官方的解释。
有些拓展并不想在框架启动阶段进行加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。
自适应拓展机制的步骤,概述下就是首先Dubbo会为拓展接口生成具有代理功能的代码。然后通过javassist、jdk编译这段代码,得到Class类。最后通过反射创建代理类。
@Adaptive
从@Target注解指定的ElementType.TYPE和ElementType.METHOD,可以看出该注解可用于类或者方法上。
-
注解在类上。Dubbo不会为该类生成代理类。在Dubbo中,仅有两个类被该注解标注,也就是AdaptiveCompiler和AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。
-
注解在方法上。表示拓展的加载逻辑由框架自动生成。
该注解提供的属性如下:
决定注入哪个目标拓展。目标拓展的名字由URL的参数传递。
比如说,对于value给定一个 new String[] {“key1”, “key2”}。
先找URL中key1参数是否存在,如果存在,则作为拓展点的名字。
如果key1参数不存在,则寻找key2参数。
如果key1、key2在URL中都不存在,则抛出IllegalStateException。
当然了,如果对value没有指定任何值,则生成默认的拓展点的名字进行使用。
源码分析
ExtensionLoader#getAdaptiveExtension()
从缓存中获取自适应拓展。如果缓存未命中,则创建自适应拓展,设置自适应拓展到缓存中。
调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象。
通过反射进行实例化。
调用 injectExtension 方法向拓展实例中注入依赖。
调用 getExtensionClasses 获取所有的拓展类。
检查缓存,若缓存不为空,则返回缓存。
若缓存为空,则调用 createAdaptiveExtensionClass 创建自适应拓展类。
尝试从缓存中获取,如果未命中,则加载所有拓展实现类,并将其放入缓存中。
加载所有拓展实现类。
获取@SPI注解指定的值,作为默认的拓展点的名字。
从指定目录下加载所有拓展点实现类。
加载所有拓展点实现类。
加载拓展点的处理逻辑。
构建自适应拓展代码。
获取编译器实现类。
编译代码,生成Class。
判断是否有标注@Adaptive注解的方法。
拼接包信息、imports信息、类声明。
- CODE_PACKAGE = “package %s;\n”
- CODE_IMPORTS = “import %s;\n”
- CODE_CLASS_DECLARATION = “public class %s$Adaptive implements %s {\n”
以Dubbo的Protocol为例,生成的代码如下:
拼接方法信息。
- CODE_METHOD_DECLARATION = “public %s %s(%s) %s {\n%s}\n”。
- CODE_METHOD_ARGUMENT = “%s arg%d”
- CODE_METHOD_THROWS = “throws %s”
Dubbo不会为没有标注@Adaptive注解的方法生成代理逻辑。
- CODE_UNSUPPORTED = “throw new UnsupportedOperationException(“The method %s of interface %s is not adaptive method!”);\n”
下面分析Dubbo如何为标注@Adaptive注解的方法生成代理逻辑的。
获取方法参数中URL类型的索引序号。
- CODE_URL_NULL_CHECK =
if (arg%d == null) throw new IllegalArgumentException(\"url == null\");\n%s url = arg%d;\n
拼接getUrl()方法。
比如说对于如下方法,我们需要获取URL数据。
拼接的字符串代码类似于如下:
获取@Adaptive注解值。
判断方法参数中是否有Invocation类型的参数。
- CLASSNAME_INVOCATION = “org.apache.dubbo.rpc.Invocation”
为Invocation类型的参数做判空判断。
-
CLASSNAME_INVOCATION = “org.apache.dubbo.rpc.Invocation”
-
CODE_INVOCATION_ARGUMENT_NULL_CHECK =
if (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");
String methodName = arg%d.getMethodName();\n
生成拓展名获取逻辑。
可选如下方式:
- CODE_EXT_NAME_ASSIGNMENT = “String extName = %s;\n”
生成拓展名判空检查。
- CODE_EXT_NAME_NULL_CHECK =
if(extName == null)
throw new IllegalStateException(\"Failed to get extension (%s) name from url (\" + url.toString() + \") use keys(%s)\");\n
生成getExtension方法。
- CODE_EXTENSION_ASSIGNMENT =
%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);\n
生成返回值从句。
- CODE_EXTENSION_METHOD_INVOKE_ARGUMENT = “arg%d”