环境搭建
- Github上下载Dubbo最新发布版本,楼主下载版本为2.5.7。
cd到源码解压目录,maven编译,命令为:
1mvn clean install -Dmaven.test.skip生成Intellij idea相关配置文件,命令为:
1mvn idea:idea双击运行生成的
dubbo-parent.ipr
文件
Java SPI
SPI是Service Provider Interfaces的简称,是Java中定义的一个很重要的规范,SPI使得应用之间变得更灵活、程序间更解耦。
一般在应用中会定义一个接口,具体的实现由对应的实现类去完成,即服务提供者(Service Provider)。模块与模块之间基于接口编程,模块之间不能对实现类进行硬编码、不能在代码里写具体的实现类,否则就违反了“可插拔原则”,如果要替换一种实现,就需要修改代码。此时,SPI提供了一种服务发现机制,完美解决了这个问题。
SPI机制基本思路是通过JDK提供的
java.util.ServiceLoader
类去主动发现服务,不需要硬编码具体的类。
当服务接口有多个实现类(即服务提供者)时,在jar包的META-INF/services/目录下创建一个以服务接口命名的文件,文件内容是该服务接口的具体实现类的全类名,一行记录是一个实现类的全类名。当外部程序装配这个模块时,通过jar包的META-INF/services/目录里的配置文件就可以找到具体的实现类名,从而进行实例化、完成模块的注入。
Java SPI 示例
定义服务接口:
该服务接口的两个实现类如下:
|
|
在resources下新建目录META-INF/services/,在目录下新建文件。文件名为服务接口全名jdkspi.WorkerService
,具体内容如下
测试示例执行入口:
执行测试示例后,结果如下:
ServiceLoader源码分析
ServiceLoader是一个final类,不能被继承,实现了Iterable接口,可以遍历,如下:
ServiceLoader属性如下:
- PREFIX: 定义了配置文件的路径,是一个final类型常量,不能设置不能更改。表面Java SPI配置文件默认放在
META-INF/services/
路径下 - service: 定义服务接口类,final类型变量,一旦被赋值便不能修改,由load方法传入
- loader: 类加载器,一旦被赋值便不能修改
- acc: 访问控制上下文,一旦被赋值比昂不修改
- providers: 存储服务提供者,也即具体实现类。存储的顺序为配置文件中实现类的排列先后顺序
- lookupIterator: 迭代器,实现延迟加载的效果
ServiceLoader只有一个构造器,且是内部构造器。不能再外部直接通过new命令创建实例对象。如下:
ServiceLoader提供了三种静态类方法来创建实例对象。如下:
load(Class<S> service)
: 利用当前线程持有的ClassLoader创建实例load(Class<S> service, ClassLoader loader)
: 利用指定的ClassLoader创建实例loadInstalled(Class<S> service)
: 利用系统顶级ClassLoader创建实例
ServiceLoader提供iterator()方法用以生成迭代器。迭代器中方法内部具体由lookupIterator实现。如下:
lookupIterator是LazyIterator的对象实例,LazyIterator是一个内部类,实现了Iterator接口,源码如下:
从上面源码中,不难发现:服务提供者的实例化过程是在具体调用时进行的,延迟加载。
Java SPI机制的ServiceLoader缺点:
- 每次获取一个实现类都必须遍历加载所有的实现类,即使是不想使用的实现类也加载了,造成了资源的浪费。
- 不能定向获取对应的实现类,必须iterator遍历查找,比较慢
Dubbo拓展机制
Dubbo拓展机制应用的就是Java SPI的思想。Java SPI配置文件中一条记录是一个实现类全名,但Dubbo配置文件中存储的是key-value键值对,value存储的是实现类全名。示例如下:
类似Java SPI机制的ServiceLoader,Dubbo中也有一个拓展加载器ExtensionLoader。ExtensionLoader中定义了配置文件的存储路径:
ExtensionLoader构造器也是内部构造器,在外部不能直接通过new命令来创建对象实例:
同样,ExtensionLoader提供静态类方法getExtensionLoader来生成实例:
调用getExtension方法可以根据name值获取指定的拓展实现类,实例化后的拓展实现类以Holder类封装存储在cachedInstances中,cachedInstances是ConcurrentMap<String, Holder<Object>>
变量。:
getExtension方法中采用了double-check机制,拓展实现类的实例化是在createExtension方法中完成的:
方法实现大体流程为:
- name作为key值,获取对应的class。在getExtensionClasses中是有做同步处理的
- 根据得到的class创建实例
- 对实例化对象进行依赖注入
- 对依赖注入后的实例化对象进行包装
依赖注入及包装源码如下:
此即是Dubbo拓展机制的大体流程,跟Java SPI机制非常类似,可看作Java SPI机制的一个优化与拓展。下一节将探讨provider服务的发布过程