dubbo源码一:dubbo的SPI机制

概念

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。

Dubbo 改进了 JDK 标准的 SPI 的以下问题:

1.JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。

2.如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。

3.增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

 

用法

 

Demo

 

源码分析

上面的demo中,我们首先通过 ExtensionLoader 的 getExtensionLoader 方法获取一个 ExtensionLoader 实例,然后再通过 ExtensionLoader 的 getExtension 方法获取拓展类对象。

ExtensionLoader顾名思义表示一个扩展点加载器,其getExtensionLoader方法会根据传入的扩展点类型取该扩展点的加载器,并且通过一个ConcurrentHashMap来缓存加载器,下次取时直接从缓存中取,可以理解为一个扩展点对应唯一的一个加载器:

可以看到上面是直接以扩展点类型为参数创建了一个新的实例:

这里有两个非常重要的属性:

1.Class<?> type:

表示当前ExtensionLoader实例是哪个接口的扩展点加载器

2.ExtensionFactory objectFactory:

表示ExtensionFactory这个扩展点的实例,只不过这个实例有点特殊,后面会讲到,同时只有type不是ExtensionFactory时才存在,这个ExtensionFactory的作用是用来依赖注入时获取具体实例的。

此时我们得到的ExtensionLoader已经存在了上面两个属性,执行其getExtension方法:

Holder,顾名思义,用于持有目标对象,也是通过一个ConcurrentHashMap来缓存了对象:

上面代码的逻辑比较清晰,首先检查缓存,缓存未命中则创建扩展对象。核心的创建扩展对象的方法是createExtension:

createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:

1.通过 getExtensionClasses 获取所有的拓展类

2.通过反射创建拓展对象

3.向拓展对象中注入依赖

4.将拓展对象包裹在相应的 Wrapper 对象中

5.回调生命周期初始化方法

以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现,我们一个一个的讲:

 

getExtensionClasses-扫描&加载指定的接口的所有扩展类

 

这里也是先检查缓存,若缓存未命中,则通过 synchronized 加锁。加锁后再次检查缓存,并判空。此时如果 classes 仍为 null,则通过 loadExtensionClasses 加载拓展类。下面分析loadExtensionClasses 方法的逻辑。

loadExtensionClasses 方法总共做了两件事情:

1.对 SPI 注解进行解析:

如果扩展接口上加了@SPI注解的话,会提取其value属性,作为默认的扩展名缓存到了cachedDefaultName成员变量中:

 

2.调用 loadDirectory 方法加载指定文件夹配置文件:

这里dubbo指定了三个可用的资源目录:

 

会分别调用loadDirectory方法从这三个目录下扫描扩展类:

最终的fileName就是目录路径+type的全限定名,符合demo中的演示,然后从工程下找到所有同名的文件调用loadResource方法加载资源:

 

loadResource 方法用于读取和解析配置文件,并通过反射加载类,最后调用 loadClass 方法进行其他操作:

 

loadClass 方法用于主要用于加载各个组件,该方法的逻辑如下:

 

这里理论上来说只要缓存name-Class的映射关系就好了,不过dubbo考虑的更加全面。

主要做了三件事情:

 

1.检测目标类上是否有 @Adaptive 注解,缓存到cachedAdaptiveClass中

标注了@Adaptive 注解的扩展类是一个特殊的扩展类,其相当于代理了所有扩展类的一个自适应类,后面会讲到。

2.检测目标类是不是一个wrapper类,缓存到cachedWrapperClasses中

warpper类也是一个特殊的扩展类,静态代理了我们的扩展类。这里是通过判断是否存在一个传入扩展类的构造方法来判断是不是一个wrapper类。

3.缓存name-Class的映射关系

这个时候上面一开始new的那个extensionClasses就存在映射关系了

4.如果类上加了@Activate注解的话,缓存下name-Activate注解对象的映射

@Activate注解注解后面也会讲到

 

injectExtension-dubbo的IOC

依赖注入的逻辑是这样的:

1.根据当前实例的类,找到这个类中的setter方法,进行依赖注入

2.分析出setter方法的参数类型pt

3.截取出setter方法所对应的属性名property

4.调用objectFactory.getExtension(pt, property)得到一个对象,这里就会从Spring容器或通过DubboSpi机制得到一个对象,比较特殊的是,如果是通过DubboSpi机制得到的对象,是pt这个类型的一个自适应对象。

5.再反射调用setter方法进行注入。

这里就用到了objectFactory这个对象,从一开始的初始化中,这个对象是通过ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());得到的ExtensionLoader.getExtensionLoader(ExtensionFactory.class)现在很清楚了,是拿到ExtensionFactory这个扩展类的扩展类加载器。

前面也说过了getAdaptiveExtension方法是拿到了一个特殊的扩展类,现在关键是如何拿到的呢:

上面也是对拿到的实例做了缓存,主要是createAdaptiveExtension方法:

 

这里可以分析出来getAdaptiveExtensionClass肯定是拿到了这个特殊扩展类的Class对象,然后通过newInstance方法反射构造出了对象。injectExtension方法其实并不会执行,因为当前的ExtensionLoader实例化时传入的type是ExtensionFactory,所以objectFactory是null,当objectFactory是null时,injectExtension方法不会做任何事情。

所以我们关键看getAdaptiveExtensionClass这个方法是如何生成Class对象的就行了:

这里可以看到同样使用getExtensionClasses方法扫描出了所有的扩展类(这个时候是ExtensionFactory的扩展类),之前有说过如果扫描到的目标类上有 @Adaptive 注解的话,会缓存到cachedAdaptiveClass中,之前没有说其是干什么的。到这里其实就是取出这个cachedAdaptiveClass并实例化,所以最终ExtensionLoader#getAdaptiveExtension方法得到的就是这个加了 @Adaptive注解的扩展类。

我们已ExtensionFactory为例,dubbo中对其实现的扩展类有三个:

1.AdaptiveExtensionFactory

负责从SpiExtensionFactory或SpringExtensionFactory中得到扩展点实例对象

2.SpiExtensionFactory

利用Dubbo的Spi机制获取一个扩展点实例

3.SpringExtensionFactory

从Spring的ApplicationContext中获取bean作为一个扩展点实例

 

我们看下AdaptiveExtensionFactory类:

可以看到其加了@Adaptive注解,而这个类的真正作用就是用来代理其他的扩展类的。如果结合SpiExtensionFactory和SpringExtensionFactory的源码,就可以知道AdaptiveExtensionFactory可以根据对应的扩展点是否有@SPI注解来判断到底是从Spring中取实例还是通过dubbo的SPI机制取实例。

所以ExtensionLoader#getAdaptiveExtension得到的就是一个当前扩展点的自适应扩展类。

进一步可以了解到dubbo中对扩展类的依赖注入可以根据注入的对象类型是否是@SPI注解类,来从Spring容器中或者dubbo的SPI机制来获取实例,从而完成属性的注入。

 

这里其实还有一种特殊的情况,就是当扫描出来的扩展类都没有加@Adaptive注解时,当调用ExtensionLoader#getAdaptiveExtension时,dubbo会自动生成一个自适应扩展类:

前提条件是扩展接口至少有一个代理方法上面须要加上方法级别的@Adaptive注解。

关于@Adaptive更细致的源码分析请参考官方源码导读-自适应拓展机制

 

dubbo的AOP

dubbo的AOP,就是利用Wrapper,如果一个接口的扩展点中包含了多个Wrapper类,那么在实例化完某个扩展点后,就会利用这些Wrapper类对这个实例进行包裹。

比如:现在有一个DubboProtocol的实例,同时对于Protocol这个接口还有很多的Wrapper,比如ProtocolFilterWrapper、ProtocolListenerWrapper,那么,当对DubboProtocol的实例完成了IOC之后,就会先调用new ProtocolFilterWrapper(DubboProtocol实例)生成一个新的Protocol的实例,再对此实例进行IOC,完了之后,会再调用new ProtocolListenerWrapper(ProtocolFilterWrapper实例)生成一个新的Protocol的实例,然后进行IOC,从而完成DubboProtocol实例的AOP。

 

再来看下方法:

cachedWrapperClasses在前面的getExtensionClasses方法扫描扩展类时已经赋值了,可以看到其实就是针对源对象做了一层层的包装。

 

initExtension-扩展类的生命周期回调

扩展类可以实现Lifecycle接口,在首次初始化时会回调initialize方法:

 

ExtensionLoader中有三个常用的方法:

  1. getExtension("dubbo"):表示获取名字为dubbo的扩展点实例
  2. getAdaptiveExtension():表示获取一个自适应的扩展点实例
  3. getActivateExtension(URL url, String[] values, String group):表示一个可以被url激活的扩展点实例,后文详细解释

 

扩展点特性

扩展点自动包装

参考Wrapper相关的源码分析。

扩展点自动装配&扩展点自适应

参考dubbo的IOC相关的源码分析。

扩展点自动激活

参考dubbo关于@Activate源码分析。

结合ExtensionLoader#getActivateExtension方法

每个扩展点都有一个name,通过这个name可以获得该name对应的扩展点实例,但是有的场景下,希望一次性获得多个扩展点实例,可以通过传入多个name来获取,可以通过识别URL上的信息来获取:

extensionLoader.getActivateExtension(url, new String[]{"car1", "car2"}); 这个可以拿到name为car1和car2的扩展类实例,同时还会通过传入的url寻找可用的扩展类, 怎么找的呢?

在一个扩展点类上,可以添加@Activate注解,这个注解的属性有:

1. String[] group():表示这个扩展点是属于拿组的,这里组通常分为PROVIDER和CONSUMER,表示该扩展点能在服务提供者端,或者消费端使用。

2. String[] value():指示的是URL中的某个参数key,当利用getActivateExtension方法来寻找扩展点时,如果传入的url中包含的参数的所有key中,包括了当前扩展点中的value值,那么则表示当前url可以使用该扩展点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值