三歪问我Dubbo的SPI机制是啥?

前言

上一篇 Dubbo 文章敖丙已经带了你们过了一遍总体的架构,也提到了 Dubbo 的成功离不开它采用微内核设计+SPI扩展,使得有特殊需求的接入方能够自定义扩展,作定制的二次开发。java

良好的扩展性对于一个框架而言尤为重要,框架顾名思义就是搭好核心架子,给予用户简单便捷的使用,同时也须要知足他们定制化的需求。sql

Dubbo 就依靠 SPI 机制实现了插件化功能,几乎将全部的功能组件作成基于 SPI 实现,而且默认提供了不少能够直接使用的扩展点,实现了面向功能进行拆分的对扩展开放的架构。
三歪问我Dubbo的SPI机制是啥?数据库

什么是 SPI

首先咱们得先知道什么叫 SPI。apache

SPI (Service Provider Interface),主要是用来在框架中使用的,最多见和莫过于咱们在访问数据库时候用到的java.sql.Driver接口了。编程

你想一下首先市面上的数据库五花八门,不一样的数据库底层协议的大不相同,因此首先须要定制一个接口,来约束一下这些数据库,使得 Java 语言的使用者在调用数据库的时候能够方便、统一的面向接口编程。缓存

数据库厂商们须要根据接口来开发他们对应的实现,那么问题来了,真正使用的时候到底用哪一个实现呢?从哪里找到实现类呢?架构

这时候 Java SPI 机制就派上用场了,不知道到底用哪一个实现类和找不到实现类,咱们告诉它不就完事了呗。app

你们都约定好将实现类的配置写在一个地方,而后到时候都去哪一个地方查一下不就知道了吗?

三歪问我Dubbo的SPI机制是啥?

Java SPI 就是这样作的,约定在 Classpath 下的 META-INF/services/ 目录里建立一个以服务接口命名的文件,而后文件里面记录的是此 jar 包提供的具体实现类的全限定名。框架

这样当咱们引用了某个 jar 包的时候就能够去找这个 jar 包的 META-INF/services/ 目录,再根据接口名找到文件,而后读取文件里面的内容去进行实现类的加载与实例化。ide

好比咱们看下 MySQL 是怎么作的。
三歪问我Dubbo的SPI机制是啥?

再来看一下文件里面的内容。
三歪问我Dubbo的SPI机制是啥?

MySQL 就是这样作的,为了让你们更加深入的理解我再简单的写一个示例。

Java SPI 示例

三歪问我Dubbo的SPI机制是啥?

而后我在 META-INF/services/ 目录下建了个以接口全限定名命名的文件,内容以下

com.demo.spi.NuanNanAobing
com.demo.spi.ShuaiAobing

运行以后的结果以下

三歪问我Dubbo的SPI机制是啥?

Java SPI 源码分析

以前的文章我也提到了 Dubbo 并无用 Java 实现的 SPI,而是自定义 SPI,那确定是 Java SPI 有什么不方便的地方或者劣势。

所以丙带着你们先深刻了解一下 Java SPI,这样才能知道哪里很差,进而再和 Dubbo SPI 进行对比的时候会更加的清晰其优点。

你们看到源码不要怕,丙已经给你们作了注释,而且逻辑也不难的,想要变强源码不可或缺。为了让你们更好的理解,丙在源码分析完了以后还会画个图,帮你们再理一下思路。

从上面个人示例中能够看到ServiceLoader.load()其实就是 Java SPI 入口,咱们来看看到底作了什么操做。
三歪问我Dubbo的SPI机制是啥?

我用一句话归纳一下,简单的说就是先找当前线程绑定的 ClassLoader,若是没有就用 SystemClassLoader,而后清除一下缓存,再建立一个 LazyIterator。

那如今重点就是 LazyIterator了,从上面代码能够看到咱们调用了 hasNext() 来作实例循环,经过 next() 获得一个实例。而 LazyIterator 其实就是 Iterator 的实现类。咱们来看看它到底干了啥。
三歪问我Dubbo的SPI机制是啥?

无论进入 if 分支仍是 else 分支,重点都在我框出来的代码,接下来就进入重要时刻了!
三歪问我Dubbo的SPI机制是啥?

能够看到这个方法其实就是在约定好的地方找到接口对应的文件,而后加载文件而且解析文件里面的内容。

咱们再来看一下 nextService()。

三歪问我Dubbo的SPI机制是啥?
因此就是经过文件里填写的全限定名加载类,而且建立其实例放入缓存以后返回实例。

总体的 Java SPI 的源码解析已经完毕,是否是很简单?就是约定一个目录,根据接口名去那个目录找到文件,文件解析获得实现类的全限定名,而后循环加载实现类和建立其实例。

我再用一张图来带你们过一遍。
三歪问我Dubbo的SPI机制是啥?

想一下 Java SPI 哪里很差

相信你们一眼就能看出来,Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件而且将实现类所有实例化,假设一个实现类初始化过程比较消耗资源且耗时,可是你的代码里面又用不上它,这就产生了资源的浪费。

因此说 Java SPI 没法按需加载实现类。

Dubbo SPI

所以 Dubbo 就本身实现了一个 SPI,让咱们想一下按需加载的话首先你得给个名字,经过名字去文件里面找到对应的实现类全限定名而后加载实例化便可。

Dubbo 就是这样设计的,配置文件里面存放的是键值对,我截一个 Cluster 的配置。
三歪问我Dubbo的SPI机制是啥?

而且 Dubbo SPI 除了能够按需加载实现类以外,增长了 IOC 和 AOP 的特性,还有个自适应扩展机制。

咱们先来看一下 Dubbo 对配置文件目录的约定,不一样于 Java SPI ,Dubbo 分为了三类目录。

  • META-INF/services/ 目录:该目录下的 SPI 配置文件是为了用来兼容 Java SPI 。

  • META-INF/dubbo/ 目录:该目录存放用户自定义的 SPI 配置文件。

  • META-INF/dubbo/internal/ 目录:该目录存放 Dubbo 内部使用的 SPI 配置文件。

Dubbo SPI 简单实例

用法非常简单,我就拿官网上的例子来展现一下。

首先在 META-INF/dubbo 目录下按接口全限定名创建一个文件,内容以下:

optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

而后在接口上标注@SPI 注解,以代表它要用SPI机制,相似下面这个图(我就是拿 Cluster 的图举个例子,和这个示例代码定义的接口不同)。
三歪问我Dubbo的SPI机制是啥?

接着经过下面的示例代码便可加载指定的实现类。
三歪问我Dubbo的SPI机制是啥?

再来看一下运行的结果。
三歪问我Dubbo的SPI机制是啥?

Dubbo 源码分析

这次分析的源码版本是 2.6.5

相信经过上面的描述你们已经对 Dubbo SPI 已经有了必定的认识,接下来咱们来看看它的实现。

从上面的示例代码咱们知道 ExtensionLoader 好像就是重点,它是相似 Java SPI 中 ServiceLoader 的存在。

咱们能够看到大体流程就是先经过接口类找到一个 ExtensionLoader ,而后再经过 ExtensionLoader.getExtension(name) 获得指定名字的实现类实例。

咱们就先看下 getExtensionLoader() 作了什么。
三歪问我Dubbo的SPI机制是啥?

很简单,作了一些判断而后从缓存里面找是否已经存在这个类型的 ExtensionLoader ,若是没有就新建一个塞入缓存。最后返回接口类对应的 ExtensionLoader 。

咱们再来看一下 getExtension() 方法,从现象咱们能够知道这个方法就是从类对应的 ExtensionLoader 中经过名字找到实例化完的实现类。
三歪问我Dubbo的SPI机制是啥?

能够看到重点就是 createExtension(),咱们再来看下这个方法干了啥。
三歪问我Dubbo的SPI机制是啥?

总体逻辑很清晰,先找实现类,判断缓存是否有实例,没有就反射建个实例,而后执行 set 方法依赖注入。若是有找到包装类的话,再包一层。

到这步为止我先画个图,你们理一理,仍是很简单的。
三歪问我Dubbo的SPI机制是啥?

那么问题来了 getExtensionClasses() 是怎么找的呢?injectExtension() 如何注入的呢(其实我已经说了set方法注入)?为何须要包装类呢?

getExtensionClasses

这个方法进去也是先去缓存中找,若是缓存是空的,那么调用 loadExtensionClasses,咱们就来看下这个方法。

三歪问我Dubbo的SPI机制是啥?
而 loadDirectory里面就是根据类名和指定的目录,找到文件先获取全部的资源,而后一个一个去加载类,而后再经过loadClass去作一下缓存操做。
三歪问我Dubbo的SPI机制是啥?

能够看到,loadClass 以前已经加载了类,loadClass 只是根据类上面的状况作不一样的缓存。分别有 Adaptive 、WrapperClass 和普通类这三种,普通类又将Activate记录了一下。至此对于普通的类来讲整个 SPI 过程完结了。
三歪问我Dubbo的SPI机制是啥?

接下来咱们分别看不是普通类的几种东西是干啥用的。

Adaptive 注解 - 自适应扩展

在进入这个注解分析以前,咱们须要知道 Dubbo 的自适应扩展机制。

咱们先来看一个场景,首先咱们根据配置来进行 SPI 扩展的加载,可是我不想在启动的时候让扩展被加载,我想根据请求时候的参数来动态选择对应的扩展。

怎么作呢?

Dubbo 经过一个代理机制实现了自适应扩展,简单的说就是为你想扩展的接口生成一个代理类,能够经过JDK 或者 javassist 编译你生成的代理类代码,而后经过反射建立实例。

这个实例里面的实现会根据原本方法的请求参数得知须要的扩展类,而后经过 ExtensionLoader.getExtensionLoader(type.class).getExtension(从参数得来的name),来获取真正的实例来调用。

我从官网搞了个例子,你们来看下。
三歪问我Dubbo的SPI机制是啥?

如今你们应该对自适应扩展有了必定的认识了,咱们再来看下源码,到底怎么作的。
三歪问我Dubbo的SPI机制是啥?

这个注解就是自适应扩展相关的注解,能够修饰类和方法上,在修饰类的时候不会生成代理类,由于这个类就是代理类,修饰在方法上的时候会生成代理类。

Adaptive 注解在类上

好比这个 ExtensionFactory 有三个实现类,其中一个实现类就被标注了 Adaptive 注解。
三歪问我Dubbo的SPI机制是啥?
三歪问我Dubbo的SPI机制是啥?

在 ExtensionLoader 构造的时候就会去经过getAdaptiveExtension 获取指定的扩展类的 ExtensionFactory。
三歪问我Dubbo的SPI机制是啥?

咱们再来看下 AdaptiveExtensionFactory 的实现。
三歪问我Dubbo的SPI机制是啥?

能够看到先缓存了全部实现类,而后在获取的时候经过遍历找到对应的 Extension。

咱们再来深刻分析一波 getAdaptiveExtension 里面到底干了什么。
三歪问我Dubbo的SPI机制是啥?

到这里其实已经和上文分析的 getExtensionClasses中loadClass 对 Adaptive 特殊缓存相呼应上了。
三歪问我Dubbo的SPI机制是啥?

Adaptive 注解在方法上

注解在方法上则须要动态拼接代码,而后动态生成类,咱们以 Protocol 为例子来看一下。
三歪问我Dubbo的SPI机制是啥?

Protocol 没有实现类注释了 Adaptive ,可是接口上有两个方法注解了 Adaptive ,有两个方法没有。

所以它走的逻辑应该应该是 createAdaptiveExtensionClass,
三歪问我Dubbo的SPI机制是啥?

具体在里面如何生成代码的我就再也不深刻了,有兴趣的本身去看吧,我就把成品解析一下,就差很少了。
三歪问我Dubbo的SPI机制是啥?

我美化一下给你们看看。
三歪问我Dubbo的SPI机制是啥?

能够看到会生成包,也会生成 import 语句,类名就是接口加个$Adaptive,而且实现这接口,没有标记 Adaptive 注解的方法调用的话直接抛错。

咱们再来看一下标注了注解的方法,我就拿 export 举例。
三歪问我Dubbo的SPI机制是啥?

就像我前面说的那样,根据请求的参数,即 URL 获得具体要调用的实现类名,而后再调用 getExtension 获取。

整个自适应扩展流程以下。
三歪问我Dubbo的SPI机制是啥?

WrapperClass - AOP

包装类是由于一个扩展接口可能有多个扩展实现类,而这些扩展实现类会有一个相同的或者公共的逻辑,若是每一个实现类都写一遍代码就重复了,而且比较很差维护。

所以就搞了个包装类,Dubbo 里帮你自动包装,只须要某个扩展类的构造函数只有一个参数,而且是扩展接口类型,就会被断定为包装类,而后记录下来,用来包装别的实现类。
三歪问我Dubbo的SPI机制是啥?

简单又巧妙,这就是 AOP 了。

injectExtension - IOC

直接看代码,很简单,就是查找 set 方法,根据参数找到依赖对象则注入。
三歪问我Dubbo的SPI机制是啥?

这就是 IOC。

Activate 注解

这个注解我就简单的说下,拿 Filter 举例,Filter 有不少实现类,在某些场景下须要其中的几个实现类,而某些场景下须要另外几个,而 Activate 注解就是标记这个用的。

它有三个属性,group 表示修饰在哪一个端,是 provider 仍是 consumer,value 表示在 URL参数中出现才会被激活,order 表示实现类的顺序。

总结

先放个上述过程完整的图。
三歪问我Dubbo的SPI机制是啥?

而后咱们再来总结一下,今天丙先带你们了解了下什么是 SPI,写了个简单示例,而且进行了 Java SPI 源码分析。

得知了 Java SPI 会一次加载和实例化全部的实现类。

而 Dubbo SPI 则本身实现了 SPI,能够经过名字实例化指定的实现类,而且实现了 IOC 、AOP 与 自适应扩展 SPI 。

总体而言不是很难,也不会很绕,你们看了文章以后若是本身再过一遍收获会更大。

我是敖丙,你知道的越多,你不知道的越多,咱们下期见!

相关文章
相关标签/搜索