Dubbo架构篇 - 自适应拓展


前言

关于自适应拓展机制,这里引用一下Dubbo官方的解释。

有些拓展并不想在框架启动阶段进行加载,而是希望在拓展方法被调用时,根据运行时参数进行加载。

自适应拓展机制的步骤,概述下就是首先Dubbo会为拓展接口生成具有代理功能的代码。然后通过javassist、jdk编译这段代码,得到Class类。最后通过反射创建代理类。


@Adaptive

Dubbo架构篇 - 自适应拓展

从@Target注解指定的ElementType.TYPE和ElementType.METHOD,可以看出该注解可用于类或者方法上。

  • 注解在类上。Dubbo不会为该类生成代理类。在Dubbo中,仅有两个类被该注解标注,也就是AdaptiveCompiler和AdaptiveExtensionFactory。此种情况,表示拓展的加载逻辑由人工编码完成。

  • 注解在方法上。表示拓展的加载逻辑由框架自动生成。


该注解提供的属性如下:

Dubbo架构篇 - 自适应拓展

决定注入哪个目标拓展。目标拓展的名字由URL的参数传递。

比如说,对于value给定一个 new String[] {“key1”, “key2”}。
先找URL中key1参数是否存在,如果存在,则作为拓展点的名字。
如果key1参数不存在,则寻找key2参数。
如果key1、key2在URL中都不存在,则抛出IllegalStateException。
当然了,如果对value没有指定任何值,则生成默认的拓展点的名字进行使用。


源码分析

ExtensionLoader#getAdaptiveExtension()

Dubbo架构篇 - 自适应拓展

从缓存中获取自适应拓展。如果缓存未命中,则创建自适应拓展,设置自适应拓展到缓存中。


Dubbo架构篇 - 自适应拓展

调用 getAdaptiveExtensionClass 方法获取自适应拓展 Class 对象。
通过反射进行实例化。
调用 injectExtension 方法向拓展实例中注入依赖。


Dubbo架构篇 - 自适应拓展

调用 getExtensionClasses 获取所有的拓展类。
检查缓存,若缓存不为空,则返回缓存。
若缓存为空,则调用 createAdaptiveExtensionClass 创建自适应拓展类。


Dubbo架构篇 - 自适应拓展

尝试从缓存中获取,如果未命中,则加载所有拓展实现类,并将其放入缓存中。

Dubbo架构篇 - 自适应拓展

加载所有拓展实现类。

Dubbo架构篇 - 自适应拓展

获取@SPI注解指定的值,作为默认的拓展点的名字。

Dubbo架构篇 - 自适应拓展

从指定目录下加载所有拓展点实现类。

Dubbo架构篇 - 自适应拓展

加载所有拓展点实现类。

Dubbo架构篇 - 自适应拓展

加载拓展点的处理逻辑。


Dubbo架构篇 - 自适应拓展

构建自适应拓展代码。
获取编译器实现类。
编译代码,生成Class。


Dubbo架构篇 - 自适应拓展

判断是否有标注@Adaptive注解的方法。

Dubbo架构篇 - 自适应拓展


Dubbo架构篇 - 自适应拓展

拼接包信息、imports信息、类声明。

Dubbo架构篇 - 自适应拓展

  • CODE_PACKAGE = “package %s;\n”
  • CODE_IMPORTS = “import %s;\n”
  • CODE_CLASS_DECLARATION = “public class %s$Adaptive implements %s {\n”

以Dubbo的Protocol为例,生成的代码如下:
Dubbo架构篇 - 自适应拓展


Dubbo架构篇 - 自适应拓展

拼接方法信息。

Dubbo架构篇 - 自适应拓展

  • CODE_METHOD_DECLARATION = “public %s %s(%s) %s {\n%s}\n”。

Dubbo架构篇 - 自适应拓展

  • CODE_METHOD_ARGUMENT = “%s arg%d”

Dubbo架构篇 - 自适应拓展

  • CODE_METHOD_THROWS = “throws %s”

Dubbo架构篇 - 自适应拓展

Dubbo不会为没有标注@Adaptive注解的方法生成代理逻辑。

Dubbo架构篇 - 自适应拓展

  • CODE_UNSUPPORTED = “throw new UnsupportedOperationException(“The method %s of interface %s is not adaptive method!”);\n”

下面分析Dubbo如何为标注@Adaptive注解的方法生成代理逻辑的。

Dubbo架构篇 - 自适应拓展

Dubbo架构篇 - 自适应拓展

获取方法参数中URL类型的索引序号。

Dubbo架构篇 - 自适应拓展

  • CODE_URL_NULL_CHECK = if (arg%d == null) throw new IllegalArgumentException(\"url == null\");\n%s url = arg%d;\n

Dubbo架构篇 - 自适应拓展

拼接getUrl()方法。

比如说对于如下方法,我们需要获取URL数据。
Dubbo架构篇 - 自适应拓展

Dubbo架构篇 - 自适应拓展

拼接的字符串代码类似于如下:
Dubbo架构篇 - 自适应拓展


Dubbo架构篇 - 自适应拓展

获取@Adaptive注解值。

Dubbo架构篇 - 自适应拓展


Dubbo架构篇 - 自适应拓展

判断方法参数中是否有Invocation类型的参数。

Dubbo架构篇 - 自适应拓展

  • CLASSNAME_INVOCATION = “org.apache.dubbo.rpc.Invocation”

Dubbo架构篇 - 自适应拓展

为Invocation类型的参数做判空判断。

Dubbo架构篇 - 自适应拓展

  • 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


Dubbo架构篇 - 自适应拓展

生成拓展名获取逻辑。

可选如下方式:
Dubbo架构篇 - 自适应拓展

Dubbo架构篇 - 自适应拓展

Dubbo架构篇 - 自适应拓展

Dubbo架构篇 - 自适应拓展

  • CODE_EXT_NAME_ASSIGNMENT = “String extName = %s;\n”

Dubbo架构篇 - 自适应拓展

生成拓展名判空检查。

Dubbo架构篇 - 自适应拓展

  • 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

Dubbo架构篇 - 自适应拓展

生成getExtension方法。

Dubbo架构篇 - 自适应拓展

  • CODE_EXTENSION_ASSIGNMENT = %s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);\n

Dubbo架构篇 - 自适应拓展

生成返回值从句。

Dubbo架构篇 - 自适应拓展

  • CODE_EXTENSION_METHOD_INVOKE_ARGUMENT = “arg%d”