Dubbo良好的扩展性与两个方面是密不可分的,一是Dubbo整体架构中,在合适的场景中巧妙的使用了设计模式,二是使用Dubbo SPI机制,使Dubbo的接口与实现完全解耦。
在本次分享中,您可以了解如下知识点
Java SPI机制
Java SPI机制的缺点
Dubbo SPI配置规范
Dubbo SPI的分类与缓存
Dubbo SPI的特点
Dubbo SPI源码分析
Dubbo对IOC的支持
推荐阅读:Apache Dubbo系列:ZooKeeper注册中心
刘莅,公众号:OutOfMemoryErrorApache Dubbo系列:ZooKeeper注册中心
Java SPI机制
SPI,全称Service Provider Interface,起初是提供给厂商做定制化插件开发的。Java SPI使用策略模式,一个接口多种实现,我们只提供接口,具体实现并不在程序中直接确定,而是写在配置文件里。具体使用步骤如下:
1、定义一个接口和对应的方法。
package com.wb.service;
2、编写该接口的实现类,可以有多种实现。
package com.wb.service.impl;
3、在META-INF/services目录下新建一个文件,文件名是接口的全路径,如com.xxx.OrderService。
4、文件的具体内容是接口的实现类的全路径名称,如果接口有多个实现类,则换行编写。
com.wb.service.impl.OrderServiceImpl01
5、在业务代码中使用java.util.ServiceLoader加载具体的实现类。
import com.wb.service.OrderService;
Java SPI机制的缺点
Dubbo为什么不使用Java提供的SPI机制,而使用自己的SP?相对于Java SPI,Dubbo SPI做了一定的优化和改进。
1、Java SPI会一次性加载所有的扩展类,如果扩展类没有被使用到也会加载,很耗时。
2、如果扩展类加载失败,则连扩展的名称都获取不到,排查错误困难。
3、增加了对IOC和AOP的支持,一个扩展类可以通过setter方法注入到其他扩展类中。
Dubbo SPI机制
Dubbo SPI配置规范
Dubbo SPI和Java SPI类似,需要在META-INF/dubbo目录下放置相应的SPI配置文件,文件名是接口的全路径名,文件内容为key=扩展点实现类的全路径名,如果有多个实现类,则用换行符分割。其中,key是Dubbo SPI注解中传入的参数。另外,Dubbo SPI兼容了Java SPI,Dubbo在启动时会扫描META-INF/services/,META-INF/dubbo/、META-INF/dubbo/internal/三个路径下的SPI配置。详情可查看org.apache.dubbo.remoting.Transporter接口的实现类org.apache.dubbo.remoting.transport.netty4.NettyTransporter的配置,此处就不一一展开。
**Dubbo SPI的分类与缓存
**
Dubbo SPI根据缓存类型可以分为以下两类:
Class缓存
Dubbo SPI获取扩展类配置时,先从缓存中获取,如果缓存中没有,则加载配置文件,根据配置把Class缓存起来,但不会全部初始化。
实例缓存
基于性能考虑,Dubbo SPI不仅会缓存Class,也会缓存Class的实例对象。每次获取时先从缓存中获取,如果缓存中没有,则重新加载并缓存。这也是Dubbo SPI比Java SPI性能高的原因之一,按需加载。
也可以根据扩展类的种类分为以下类型:
普通扩展类
最基础的扩展类,在SPI中配置的扩展类。
包装扩展类(Wrapper类)
如果该类的构造方法里有普通扩展类作为参数,Dubbo会判定为扩展点 Wrapper 类。
自适应扩展类(Adaptive类)
这样的扩展接口有多种实现,具体使用哪个类不写死在配置中,而是通过URL参数动态确定(此处不展开解释)。
**Dubbo SPI的特点
**
自动包装特性
如果该类的构造方法里有普通扩展类作为参数,Dubbo会判定为扩展点 Wrapper 类。
自动加载特性
如果该扩展类的成员变量有其他扩展类作为属性,并且拥有setter方法,那么Dubbo也会注入对应的扩展点实例(类似于Spring的依赖注入)。
自适应
在Dubbo中使用@Adaptive注解,我们可以在URL中动态的传入参数来确定具体使用哪个实现类。
**Dubbo SPI源码分析
**
与Java SPI代码类似,Dubbo官网中有这么一段示例
public class DubboSPITest {
所以我们阅读源码的切入点是org.apache.dubbo.common.extension.ExtensionLoader#getExtension方法。
public T getExtension(String name) {
上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。下面我们来看一下创建扩展对象的过程是怎样的。
org.apache.dubbo.common.extension.ExtensionLoader#createExtension
private T createExtension(String name) {
createExtension 方法的逻辑稍复杂一下,包含了如下的步骤:
通过 getExtensionClasses 获取所有的拓展类
通过反射创建拓展对象
向拓展对象中注入依赖
将拓展对象包裹在相应的 Wrapper 对象中
以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现。接下来,将会重点分析 getExtensionClasses 方法的逻辑,以及简单介绍 Dubbo IOC 的具体实现。
org.apache.dubbo.common.extension.ExtensionLoader#getExtensionClasses
private Map<String, Class<?>> getExtensionClasses() {
这段代码逻辑也很简单,下面分析 loadExtensionClasses 方法的逻辑
org.apache.dubbo.common.extension.ExtensionLoader#loadExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
loadExtensionClasses 方法总共做了两件事情,一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件。SPI 注解解析过程比较简单,无需多说。下面我们来看一下 loadDirectory 做了哪些事情。
private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
loadDirectory 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource 方法加载资源。我们继续跟下去,看一下 loadResource 方法的实现
private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader,
loadResource方法用于读取配置文件,最后通过loadClass方法对结果进行缓存,loadClass方法代码如下:
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
Dubbo对IOC的支持
Dubbo IOC 是通过 setter 方法注入依赖。Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter 方法特征。若有,则通过 ObjectFactory 获取依赖对象,最后通过反射调用 setter 方法将依赖设置到目标对象中
org.apache.dubbo.common.extension.ExtensionLoader#injectExtension
private T injectExtension(T instance) {
好了,这是我读Dubbo官网所理解的知识点,今天的分享就到这里,下期再见。
扫描二维码,加作者微信,更多精彩
本文分享自微信公众号 - OutOfMemoryError(backend_technology)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。