Dubbo篇:SPI扩展点加载机制源码分析


概述


         SPI扩展点机制是Dubbo良好可扩展性的基础,几乎所有的功能组件都基于此实现的。Dubbo的SPI机制基于Java SPI实现,在其基础上做了一些改进和优化,主要在于加载失败后不会像Java SPI一样吞掉异常信息,并自己实现了IoC和AOP机制,而且也不像Java SPI一样会一次性初始化扩展点全部实现类。

         Dubbo SPI启动时会默认扫描META-INF/services/、META-INF/dubbo/、META-INF/dubbo/internal/下的配置文件,文件名称命名为接口的全路径名,配置文件内容为key=扩展点实现类全路径名,如果有多个实现类则使用换行符分隔。

         ExtensionLoader是整个扩展机制的主要逻辑类,实现了配置的加载、扩展类缓存、自适应对象生成等所有工作。入口分为getExtension、getAdaptiveExtension和getActivateExtension,分别是获取普通扩展类、获取自适应扩展类、获取自动激活扩展类。

         Dubbo扩展点提供的主要有三个注解:@SPI注解在类上,标识接口是一个扩展点,属性value指定默认适配扩展点的名称,@Activate表示一个扩展类被获取到的条件,@Adaptive 注解在扩展点 Interface 的方法上时,dubbo 动态的生成一个这个扩展点的适配扩展类(生成代码 ,动态编译实例化 Class )。



ExtensionLoader源码分析


         源码分析以协议的初始化为例:在这里插入图片描述

         每个扩展类都对应一个ExtensionLoader,该扩展类的所有实现都由用一个ExtensionLoader加载,因此ExtensionLoader只有一个私有的构造方法,但是对外提供了一个静态方法getExtensionLoader方法获取ExtensionLoader实例,代码实现如下:

在这里插入图片描述

         首先是三重校验,穿的Class是不是null,是不是接口,有没有被@SPI注解修饰,校验不通过直接抛出异常。检验通过后先尝试从EXTENSION_LOADERS中获取ExtensionLoader,EXTENSION_LOADERS是ExtensionLoader中维护的一个静态的ConcurrentHashMap,用于存储接口和ExtensionLoader的映射关系。如果为未取到实例则调用私有构造创建一个,私有构造代码如下:

在这里插入图片描述

         初始化是会新建一个ExtensionFactory工厂类赋值到objectFactory中,因为ExtensionFactory本身也是一个扩展点,主要有SpiExtensionFactory、SpringExtensionFactory、AdaptiveExtensionFactory三种实现。所以会再走一次通过ExtensionLoader获得实例流程。

         获得ExtensionLoader进入getAdaptiveExtension()方法获取自适应扩展点,代码如下:

在这里插入图片描述

         cachedAdaptiveInstance是一个Holder类型的实例缓存,先尝试调用get方法从该缓存获取实例,如果获得实例则直接返回,没有的话则创建并缓存实例。因为此处为单例模式,所以在synchronized锁中再次判断尝试获取实例并是否为空,还未获取到则调用createAdaptiveExtension()方法创建实例:

在这里插入图片描述

         createAdaptiveExtension()中先获得一个自适应实现类的Class,获得其实例,然后对其进行依赖注入,先对getAdaptiveExtensionClass()做展开:

在这里插入图片描述

         加载当前扩展点的所有实现,加载完后,如果cachedAdaptiveClass不为空(目前只有AdaptiveExtensionFactory或者AdaptiveCompiler会不为空),直接返回,否则动态生成一个实现类来返回。先看getExtensionClasses():

在这里插入图片描述

         cachedClasses是缓存了一个Map的Holder,存放当前扩展点所有实现类,同样是先尝试从缓存中取,取不到调用loadExtensionClasses()进行扫描加载然后缓存起来。
进入loadExtensionClasses()方法:

在这里插入图片描述

         获取@SPI的value标注的默认值并将其保存到cachedDefaultName中,然后从META-INF/services/、META-INF/dubbo/、META-INF/dubbo/internal/ 三个目录中加载类,loadDirectory()方法代码实现如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

         代码逻辑有点长,简而言之就是,获取类加载,读取解析配置目录下的配置文件,然后将配置的对象用获得类加载器加载,然后根据各种条件分类缓存。

         展开之后有点深,现在回到getAdaptiveExtensionClass()方法,继续分析下一句createAdaptiveExtensionClass(),代码如下:
在这里插入图片描述

         这么看整体逻辑还是很清晰,先调用createAdaptiveExtensionClassCode()生成一段文本代码,然后编译器去编译这段文本代码,编译器也是扩展点,默认是javassist实现,将文本代码生成一个class。createAdaptiveExtensionClassCode()内部代码逻辑非常长,主要是各种字符串拼接拼出一段文本代码,不再展开,贴一段生成的文本示例:

在这里插入图片描述

         到这里第一波的扩展点就加载完了,接下来就是依赖注入injectExtension()方法:

在这里插入图片描述

         主要逻辑就是获取set开头的方法,然后调用objectFactory.getExtension()获取要注入的实例,这里的objectFactory就是ExtensionLoader初始化时赋值进去的ExtensionFactory,再然后就是调用method.invoke注入进去。



         至此,一次扩展点加载流程简要分析完毕。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值