dubbo中自己实现了一套SPI,解决了dubbo的可扩展性问题,方便了框架的各层可以在多个不同的实现之间进行来回切换。
SPI的实现主要在类ExtensionLoader中,这个类的构造函数接收一个Class类型的参数,并且这个class必须是接口,必须用注解spi进行标注。有多少个可扩展接口,就有多少个ExtensionLoader的实例。
这个类中一共有三类方法:根据名字获取实现、获取某个接口的适配实现和获取某个接口的激活实现
1、根据名字获取实现
这类方法比较好理解,如下Protocol有下面这么多的实现:
等号前面的是名字,等号后面的是实现,例如:如果传入dubbo就会返回DubboProtocol
下面对这个方法的源码进行分析
这个方法比较好理解,其实就是判断缓存,如果有直接返回,没有的话通过createExtension方法进行创建
第499行调用getExtensionClasses(),会返回这个接口的所有实现,这个方法的内部会有扫描三个目录进行初始化的过程。
第509行的injectExtension方法的主要作用是对获取到的这个实现的内部的属性字段进行赋值。就是如果这个类中有的字段的类型是SPI接口或者在spring容器中可以找到对应的ben,那么就对这些字段进行赋值。
第510到515行是对当前返回的实例进行再次包装,当然是如果这个spi接口的实现有包装类的话。如果某个实现类,有单个参数的构造函数,并且参数类型是当前SPI接口,那么这个类就是包装类。他就好对当前
要返回的类,进行包装,有可能存在多个包装器类。例如上面第一张截图中的ProtocolFilterWrapper和ProtocolListenerWrapper就是两个包装器类
总结一下:
(1)初始化当前spi接口的所有实现(会扫描三个目录)到内存中
(2)根据名字返回对应的实现的实例
(3)对这个实例进行属性字段的赋值,来源于其他的spi实现或者spring容器
(4)如果当前spi接口有包装器类,对当前实现进行包装,有可能有多个包装器类
这类方法基本是不会被外面的类调用,主要是为另外两类方法服务
2、获取SPI接口的适配实现,只会返回一个实现类。
这个方法要@Adaptive注解配合实现。这个注解可以标注在类上,也可以标注在方法上,如果是标注在方法上只能是接口方法不是类方法。
(1)如果某个SPI接口的实现类,被这个注解进行了标注,那么直接返回这个实现类
(2)如果接口的方法被这个注解进行了标注,那么会利用字节码进行动态生成一个类。
如果接口像下面这么标注
那么动态生成的方法将会是下面这样
由于某个SPI接口的适配实现只会有一个,所有(1)的优先级大于(2),获取适配实现主要是解决,只有在程序运行期间才能却能使用哪个实现
3、获取SPI接口的激活实现,可以是多个
最常见的例子就是获取Filter
激活某个实现有两种方式
(1)使用注解Activate进行标注,这个注解有两个重要的参数一个是group,一个是value。这两个参数都是数组
group表示getActivateExtension方法传入的grop值如果被注解上的group数组包含,那么返回这个扩展。
value表示URL的parameter参数中,如果有一个参数的key和value数组的任何一个元素相同,那么返回这个扩展。
group和value不知道,表示不做约束,无条件激活这个实现
(2)不通过注解标注还可以在URL参数中指定实现类的名字,这个方法既可以激活某个实现,也可以去掉(1)中已经激活的实现,例如下面这个配置filter的例子
总结一下:
(1)通过注解进行标注的实现,被称为缺省扩展,包括dubbo自带的(入各种filter)和用户自定义的扩展用注解进行了标注。
(2)通过url的参数来指定实现的名字,也可以激活某个或某些实现。
(3)url的参数也可以去掉缺省扩展,规则见上面的图。
下面分析下代码:
第196行表示没有去掉全部的缺省扩展
198行变量所有的通过注解进行标注的实现
201行grop可以匹配上
203、204、205判断这个缺省扩展没有通过URL的参数去掉,isActivate判断注解的value值中任意一个元素和RUL参数的任意一个key相同
206表示此扩展被激活了,添加到list中
210排序
上面这部分代码,就是在url参数中指定扩展名字是的处理逻辑
215、216行表示没有在url参数中排除这个扩展
217就是处理和缺省扩展的排序关系