首页 > 图灵资讯 > 技术篇>正文

(一)Dubbo源码解析:增强SPI

2023-08-21 17:21:13

〇、前言

在Dubbo的架构设计中,如何通过“类插拔”灵活扩展或削弱其功能,SPI起着极其关键的作用。作为分析Dubbo源代码的第一篇文章,本文暂时放下了“服务登记发布流程”、“服务启动过程”、“请求处理流程探索这些功能代码,我们首先从最基本的问题开始,即:如何实现Dubo的增强SPI?,只有了解了这个问题,以后再看其他功能代码,才会更加轻松无阻~

一、整体时序图

先介绍一下具体的源代码细节,先动态获取AdaptiveExtensionExtension向大家展示整个时序图,通过下图,大家会对其处理过程有一个大致的了解,时序图如下:

二、源码详解

首先,作为源代码分析的入口,让我们来看看Provider端是如何通过调用dubbo的API来使用dubbo注册自己的服务的。代码如下:

在上图的代码中,我们可以看到,当我们得到它时ServiceConfig实例对象后,通过一系列赋值操作,最终调用它export()该方法实现了服务界面的注册/曝光操作;那么,我们首先需要关注的是上图中的红框部分,即通过new ServiceConfig()创建ServiceConfig例子。在创建过程中,首先执行静态全局变量的初始化操作,即下图中红框的变量创建代码,即增强SPI代码。

2.1> ExtensionLoader.getExtensionLoader(Protocol.class)

首先,让我们来分析一下ExtensionloadergetExtensionLoader(Class<T> type)该方法的入参方法type必须满足非空接口类型,并使用@SPI注释。所以EXTENSION_INSTANCES最初是空的ConcurrentHashMap。因此,有必要创建ExtensionLoader,并将其缓存到Extension_INSTANCES中。

因为type入参=Protocol.class,所以我们再来看看new ExtensionLoader(Protocol.class)结构方法,在其结构方法的内部,我们还需要赋值objectFactory,即需要调用ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()获取ExtensionFactory的适配器扩展实例。

当我们调用getExtensionLoader(ExtensionFactory.class)时,EXTENSION_INSTANCES仍然是空的。因此,type将被使用=ExtensionFactory.class再次调用入参ExtensionLoader结构方法,那么此时入参的结构方法Type等于ExtensionFactory.class,满足type == ExtensionFactory.class ? null...,所以objectFactory=null

2.2> getAdaptiveExtension()从缓存中获得适配器扩展的实例

由于在ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()在内部逻辑中,调用ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension。因此,在这里调用getAdaptiveExtension()是type=ExtensionFactory.class的方法。

在这种方法内部,通过Double Check对instance是否为null进行了双重验证。如果还是空的,我们可以通过createAdaptiveExtension()创建适配器扩展对象的方法。代码如下红框所示:

2.3> createAdaptiveExtension()创建适配器扩展实例

createAdaptiveExtension()在方法中,代码相对简单,只有一行代码,但做了两件大事:

[事件1]通过**getAdaptiveExtensionClass()获取适配器扩展类,并通过newinstance创建实例对象;<br>[事件2]通过injectextension(...)**该方法自动注入扩展点之间的依赖。

请参见下图中两个红框所示的两个事件:

2.3.1> getAdaptiveExtensionClass()

该方法的主要功能是获得适配器扩展Class对象。该方法涉及两部分:

如果找到了加载和缓存扩展的类别cachedAdaptiveClass,然后返回。<br>[其次]如果找不到,则通过代码组装源代码,并通过Compiler编译生成Classs;

以上介绍的处理步骤,请参见下图中的三个红框:

2.3.1.1> getExtensionClasses()加载指定文件名的SPI接口类

如果存在于缓存cachedclasses中,则返回。如果不存在,请调用loadExtensionClasses()该方法获得加载扩展类,并缓存到cachedclasses中

让我们看一看**loadExtensionClasses()**如何进行类加载?

在方法cachedefaultextensionnname()中,将通过三个步骤配置@SPIvalue将值缓存到cacheddefaultname中。

[步骤1]在type类别上获得@SPI注释defaultAnnotation;<br>如果[步骤2]defaultAnnotation如果不是空的,则获得注释配置的value值;<br>[步骤3]缓存value值cachedDefaultName中,供后续使用;

请参见下图中三个红框所示的上述处理步骤:

看完cachedefaultextensionnname法后,我们将视野转移到loadDirectory()在方法上,该方法用于加载filename文件并分析配置的内容。由于key和value配置在SPI配置文件中,最终将其读取到内存中:

file

loadResource()该方法用于分析filename文件中的内容。该方法主要取每行配置,然后通过配置文件中的等号(“=”)将key和value分开,即:

【key】等号左侧;<br>【value】等号的右侧;

分割后,通过loadClass加载value中配置的Class名称列表,代码如下:

loadClass()用于分析,缓存cachedadadaptiveclasss、cachedWrapperClasses、cachedActivates、cachednames和extensionClasses,具体逻辑如下:

【步骤1】如果入参clazz使用@Adaptive注释,则将此clazz缓存到cachedadaptiveclass中;<br>【步骤2】如果入参clazz是Wraper类(即入参为clazz的结构方法),将这个clazz缓存到cachedWraperclases中;<br>【步骤3】通过逗号分割value值,即在配置文件中获得扩展Class名称数组names;<br>使用@Activate注解的names类别,在cachedactivates中缓存;<br>[步骤5]遍历names数组,将name缓存到cachednames中;<br>[步骤6]将name和clazz缓存到extensionclases中;

在这里,我们需要注意的是,我们只需要对这些缓存名称有一个印象。我们不必担心缓存后要做什么。在后面的分析部分,面纱将慢慢删除。上述步骤的相关代码如下红色框所示:

2.3.1.2> createAdaptiveExtensionClass()

在分析了上述代码后,我们将回到2.3.一章代码部分,如下所示:

我们已经分析过了getExtensionClasses()方法如果SPI没有配置Adaptive类别,即cachedadaptiveClass等于null,则将执行createAdaptiveExtensionClass()该方法通过程序组装Adaptive源代码,然后默认通过Javassistcompiler将适配器源代码编译成Class对象。让我们来看看createadaptivensionclass()方法的具体处理过程。

在这种方法中,首先通过调用generate()获取java源代码的方法(String code),这就是我们前面提到的——Adaptive源代码通过程序组装,具体组装过程如下:

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()); ///[组装包路径]"package %s;\n"    code.append(generateImports()); ///【组装类引用】"import %s;\n"    code.append(generateClassDeclaration()); ///[组装声明]"public class %s$Adaptive implements %s {\n"    Method[] methods = type.getMethods();    for (Method method : methods) {        code.append(generateMethod(method)); ///[拼装方法]"public %s %s(%s) %s {\n%s}\n"    }    code.append('}');    return code.toString(); // 返回组装后的java源代码}

java源代码已经组装好了code之后,通过AdaptiveCompiler编译java源代码,生成Class类型的实例对象,如下所示:

我们可以看到,Compiler也是通过获得的getAdaptiveExtension()获得该方法的方法如下:

Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();

但由于Compiler配备了适配器AdaptiveCompiler(见下图),因此不需要通过组装源代码获得适配器。而是直接返回AdaptiveCompiler。

2.3.1.3> AdaptiveCompiler.compile()

接下来,让我们来看看Adaptivecompiler的调用。compile方法的处理过程主要有两个步骤:

[步骤1]首先,通过Dubbo增强SPI获得默认扩展compiler实例对象;<br>[步骤2]然后调用compilercompile(...)java源代码方法code编译操作;

代码如下所示:

由@SPI指定value=“javassist”,所以在执行cacheDefaultExtensionName()cachedefaultName在方法上被赋值为“javassist”。然后通过name=“javassist“为Javassistcompiler找到缓存的扩展类loader,然后我们调用JavassistcompilergetDefaultExtension()方法如下:

2.3.1.4> getExtension(...)

getExtension(...)在方法中,首先试着去holder查询之前是否创建了参考name的实例对象。因为这是我们第一次运行这种方法,我们自然会从holder中得到null对象,所以我们需要做两件事:

【步骤1】创建入参nameInstance的扩展类实例对象;<br>[步骤2]将instance维护到holder这样,如果再想得到这个例子,就可以直接从holder中得到。

所以比价复杂的方法是创建instance实例对象的方法createExtension(name)如下图红框所示:

通过name=“”javassist“从cachedclasses加载到相应的clazz=org.apache.dubbo.common.compiler.support.JavassistCompiler,因为Javassistcompiler没有缓存EXTENSION_INSTANCES所以需要调用clazz.newInstance()创建实例并缓存到EXTENSION_INSTANCES中去,如下所示:

如上所述,我们介绍了Javassistcompiler实例的结构过程**。所以,让我们把视野拉回来,看看compiler.compile(code, classLoader)其中,compiler的实例对象实际上是Javassistcompiler**的实例,如下所示

然后在compile中(...)在方法中,实际调用的是父类Abstractcompilercompilercompiler(...)虽然这种方法似乎有很多代码,但核心代码实际上是通过Classs知识的.forname生成Class实例对象的代码。如下红框所示:

2.3.2> newInstance()

为什么这里要只拿出newinstance()?其实醉翁的意思不是酒。还记得我们在2.1章介绍过Extensionfactoy获取Adaptiveeension的代码吗?为了方便大家回忆,如下红框所示:

所以,当我们调用ExtensionFactoy时newInstance()该方法将执行AdaptiveensionFactory的结构方法,该方法获得了ExtensionFactory类型的扩展加载类型loader,然后通过调用getSupportedExtensions()方法,获得“方法,获得”spi”和“spring”。以后我们就可以了。spispring为key,将org.apache.dubbo.common.extension.ExtensionFactory文件中配置的Spiextensionfactory和Springextensionfactory创建了扩展工厂的例子:

然后将SpiextensionFactory和SpringextensionFactory实例对象保存到factories用于后续调用ExtensionFactory.getExtension(...)方法时,通过遍历factories,再调用factories.getExtension(type, name)获取相应的扩展类,代码如下:

2.3.3> injectExtension()

通过这种方法,我们可以实现扩展类的注入操作。事实上,代码量并不多。主要逻辑是通过遍历instance实例的每个setter方法过滤掉“不一致”的方法。如果setter方法的参考是一个扩展类,则通过objectFactory.getExtension(pt, property)该方法获得扩展对象,并通过反射注入相应的方法&注:如下图所示:

以下是今天文章的内容:

写作并不容易。作者几个小时甚至几天就完成了一篇文章。我只想换取你几秒钟 点赞 & 分享 。

更多技术干货,欢迎关注微信官方账号“爪哇缪斯” ~ \(^o^)/ ~ 「干货分享,每天更新」

上一篇 springboot集成nacos
下一篇 GC的前置工作,聊聊GC是如何快速枚举根节点的

文章素材均来源于网络,如有侵权,请联系管理员删除。