Dubbo架构篇 - 拓展点机制之自适应拓展点

it2025-07-28  28

文章目录

前言@Adaptive源码分析


前言

关于自适应拓展机制,这里引用一下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注解的方法。


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

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

以Dubbo的Protocol为例,生成的代码如下:


2. 拼接方法信息。

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”

前面展示了代码的拼接过程,而对于 createAdaptiveExtensionClass 方法呢,最后会通过 compiler.compile(…) 方法 将传入拼接的代码和类加载器 作为参数,调用 compile 方法 进行编译这段代码。

Dubbo提供了如下两种编译器:

JavassistCompilerJdkCompiler

默认是JavassistCompiler。

(具体编译的代码 这里不再叙述。)

最新回复(0)