博客中代码地址:https://github.com/farliu/farpc.git
dubbo架构

本章不讲述其他SPI博文所说的细节,SPI博文实在太多了。但你可以在本章了解到SPI作用以及其他博文中没有解释详细的地方。继续看上面这张图,SPI其实不属于图中的任何一个环节。但是它对dubbo尤为重要,也是非常值得一说的内容。

dubbo选择SPI背景及作用

在众多dubbo源码解析的博文,都会将SPI做重点来解说,可见它对dubbo多么重要。那么我们先来了解一下SPI的背景。我在官方的quick start中找到这样一句话。
dubbo doc

大概的意思就是告诉我们,dubbo可以只依赖JDK,不依赖于任何三方库就可以运行。这其实也是dubbo设计者的一个夙求。

聊dubbo的SPI之前,我们先聊聊spring,spring最值得吹捧的功能就是IOC。而IOC自然也是整个开发历程的必然产物,因为当众多框架(MVC、ORM)诞生后,肯定是需要有这样一个东西来将多个框架粘合在一次的。而IOC就是这样一个粘合剂,它作为粘合剂实现的方案就是将大家的bean管理在一起,以此达到相互调用。

那么dubbo设计者不想让dubbo依赖任何第三方库就能运行,难道它就没有IOC这样的需求吗?自然是有的。那么dubbo选择的解决方案就是使用SPI,基于jdk原生的SPI帮助dubbo创建类的对象。那么我们暂且可以将SPI简单的理解为它帮助dubbo初始化对象。

SPI的作用,我们暂且这么理解了。那么dubbo用它来做了什么呢?我们模拟一下场景,dubbo作为一款优秀的框架,肯定需要适配各样的场景,例如:

  1. 注册中心,用户可以选择zookeeper、redis…
  2. rpc协议,用户可以选择netty、http…
  3. 负载均衡,用户需要根据自己硬件配置选择负载均衡策略

这就到了SPI的登场的时候了。dubbo对于需要提供扩展的接口,例如我们上一章的ILoadbalance,使用SPI根据用户的配置初始化对应的实现类。而程序在运行时只需要关注调用对应的接口就行了。

补充众多SPI博文中没有讲述的

说SPI,众多博客中都会提到API,简单的说,api是给使用者使用的,spi是给拓展者使用的。这句话没错,但是为什么?为什么要设计SPI?还有所谓的拓展者是谁?

从开发能力上说,框架作者能做到的功能,我们普通开发者也一定能做到,只是代码实现优雅,性能略有缺陷。作为一个使用者,肯定会有根据公司产品对某一方面定制化的需求。而dubbo作为一款通用框架,总会有一些什么地方满足不了你定制化的需求,比如说你要根据公司自己的传输协议,传输数据来调用dubbo,这个需求其实很合理。在rpc初级阶段,一些大厂都会自己尝试,就会定义各种自己公司特色的东西,那么dubbo这时肯定不支持。那么这时怎么处理,难道向dubbo开发方pull request?而分支,质量,合并,冲突都会很难管理。

以上是开发的一个痛点,那么dubbo是怎么解决的呢。他在很多地方使用SPI提供了扩展点,当你有定制化需求时,你可以根据SPI的规范,很容易的将自己定制好的某一个功能插在dubbo运行过程中。比如你要定义一个自己的协议。那么你只需要完成以下几步

  1. 实现org.apache.dubbo.rpc.Protocol。实现定制化需求。
  2. 在resource/META-INF/dubbo,新建名字为org.apache.dubbo.rpc.Protocol的文件。将自己实现的协议维护在里面。
    1
    2
    org.apache.dubbo.rpc.Protocol:
    xxx=com.xxx.XxxProtocol
  3. 在dubbo的配置中写上你的协议名字:xxx
    1
    2
    <!-- 声明协议,如果没有配置id,将以name为id -->
    <dubbo:protocol id="xxx1" name="xxx" />

那么这个时候可以回答那两个问题。为什么要设计SPI?设计SPI的目的就是为方便使用者对已有功能进行扩展,满足使用者定制化的需求。扩展者是谁?扩展者指的就是我们,当我们需要适配自己定制需求时,可以使用SPI扩展点进行扩展

dubbo扩展点可在官网查看:http://dubbo.apache.org/zh-cn/docs/dev/impls/protocol.html

SPI简单应用

本节使用jdk的SPI,尽量说明SPI的作用。

1
2
3
public interface IWelcome {
void say();
}
1
2
3
4
5
6
public class LadyWelcome implements IWelcome {
@Override
public void say() {
System.out.println("lady: welcome to SPI.");
}
}
1
2
3
4
5
6
public class GentlemenWelcome implements IWelcome {
@Override
public void say() {
System.out.println("gentlemen: welcome to SPI.");
}
}

新建文件META-INF/services/com.ofcoder.xxx.IWelcome

1
2
com.ofcoder.xxx.GentlemenWelcome
com.ofcoder.xxx.LadyWelcome

测试代码,与结果。

1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) {
ServiceLoader<IWelcome> loads = ServiceLoader.load(IWelcome.class);
for (IWelcome load : loads) {
load.say();
}
}
}
------------------------
gentlemen: welcome to SPI.
lady: welcome to SPI.