(一)Dubbo源码解析:增强SPI
2023-08-21 17:21:13
〇、前言
在Dubbo的架构设计中,如何通过“类插拔”灵活扩展或削弱其功能,SPI起着极其关键的作用。作为分析Dubbo源代码的第一篇文章,本文暂时放下了“服务登记发布流程
”、“服务启动过程
”、“请求处理流程
探索这些功能代码,我们首先从最基本的问题开始,即:如何实现Dubo的增强SPI?
,只有了解了这个问题,以后再看其他功能代码,才会更加轻松无阻~
先介绍一下具体的源代码细节,先动态获取AdaptiveExtension
和Extension
向大家展示整个时序图,通过下图,大家会对其处理过程有一个大致的了解,时序图如下:
首先,作为源代码分析的入口,让我们来看看Provider端是如何通过调用dubbo的API来使用dubbo注册自己的服务的。代码如下:
在上图的代码中,我们可以看到,当我们得到它时ServiceConfig
实例对象后,通过一系列赋值操作,最终调用它export()
该方法实现了服务界面的注册/曝光操作;那么,我们首先需要关注的是上图中的红框部分,即通过new ServiceConfig()创建ServiceConfig
例子。在创建过程中,首先执行静态全局变量的初始化操作,即下图中红框的变量创建代码,即增强SPI代码。
首先,让我们来分析一下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
;
由于在ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension()
在内部逻辑中,调用ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension
。因此,在这里调用getAdaptiveExtension()
是type=ExtensionFactory.class的方法。
在这种方法内部,通过Double Check对instance是否为null进行了双重验证。如果还是空的,我们可以通过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配置文件中,最终将其读取到内存中:
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()
方法如下:
该getExtension(...)
在方法中,首先试着去holder查询之前是否创建了参考name的实例对象。因为这是我们第一次运行这种方法,我们自然会从holder中得到null对象,所以我们需要做两件事:
【步骤1】创建入参
name
Instance的扩展类实例对象;<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”。以后我们就可以了。spi
和spring
为key,将org.apache.dubbo.common.extension.ExtensionFactory
文件中配置的Spiextensionfactory和Springextensionfactory创建了扩展工厂的例子:
然后将SpiextensionFactory和SpringextensionFactory实例对象保存到factories
用于后续调用ExtensionFactory.getExtension(...)方法时,通过遍历factories
,再调用factories.getExtension(type, name)
获取相应的扩展类,代码如下:
通过这种方法,我们可以实现扩展类的注入操作。事实上,代码量并不多。主要逻辑是通过遍历instance实例的每个setter方法过滤掉“不一致”的方法。如果setter方法的参考是一个扩展类,则通过objectFactory.getExtension(pt, property)
该方法获得扩展对象,并通过反射注入相应的方法&注:如下图所示:
以下是今天文章的内容:
写作并不容易。作者几个小时甚至几天就完成了一篇文章。我只想换取你几秒钟 点赞 & 分享 。
更多技术干货,欢迎关注微信官方账号“爪哇缪斯” ~ \(^o^)/ ~ 「干货分享,每天更新」