1.1什么是SPI ?
SPI(服务提供者接口),即服务提供方接口,是JDK内置的一种服务提供机制。在写程序的时候,一般都推荐面向接口编程,这样做的好处是:降低了程序的耦合性,有利于程序的扩展。
SPI也秉承这种理念,提供了统一的服务接口,服务提供商可以各自提供自己的具体实现。大家都熟知的JDBC中用的就是基于这种机制来发现驱动提供商,不管是甲骨文也好,MySQL也罢,在编写代码时都一样,只不过引用的jar包不同而已。后来这种理念也被运用于各种架构之中,比如达博,Eleasticsearch。
1.2 JDK SPI的小栗子
SPI的实现方式是将接口实现类的全限定名配置在文件中,由服务加载器读取配置文件,加载实现类。
了解了概念后,来看一个具体的例子。
1)定义一个接口
<代码>公共接口操作{ ,,,int, int操作(int num1 num2); }代码>
2)写两个简单的实现
<代码>公共类DivisionOperation实现操作{ ,,,,公共int操作(int, int num1 num2) { ,,,,,,System.out.println(“运行部门operation"); ,,,,,,返回num1/num2; ,,,,} }代码>
3)添加一个配置文件
在类路径路径下添加一个配置文件,文件名字是接口的全限定类名,内容是实现类的全限定类名,多个实现类用换行符分隔。
目录结构
文件内容
<代码> com.api.impl.DivisionOperation com.api.impl。PlusOperation 代码>
4)测试程序
<代码>公共类JavaSpiTest { ,,@Test ,,公共空testOperation()抛出异常{ ,,,,ServiceLoader操作=ServiceLoader.load (Operation.class); ,,,,operations.forEach(项目→System.out.println(“结果:“+项目。操作(2,2))); ,,} }代码>
5)测试结果
<代码>运行部门操作 结果:1 +操作运行 结果:4 代码>
1.3 JDK SPI的源码分析
例子很简单,实现的话,可以大胆猜测一下,看名字“即便ServiceLoader”应该就是用类加载器根据接口的类型加上配置文件里的具体实现名字将实现加载了进来。
接下来通过分析源码进一步了解其实现原理。
1.3.1即便ServiceLoader类
前缀定义了加载路径,重载方法初始化了LazyIterator, LazyIterator是加载的核心,真正实现了加载。加载的模式从名字上就可以看的出,是懒加载的模式,只有当真正调用迭代时才会加载。
1.3.2 hasNextService方法
LazyIterator中的hasNextService方法负责加载配置文件和解析具体的实现类名。
1.3.3 nextService方法
LazyIterator中的nextService方法负责用反射加载实现类。
看完了源码,感觉这个代码是有优化空间的,实例化所有实现其实没啥必要,一来比较耗时,二来浪费资源.Dubbo就没有使用Java原生的SPI机制,而是对其进行了增强,使其能够更好地满足需求。
二、达博SPI
2.1达博SPI的小栗子
老习惯,在拆解源码之前,先来个栗子。此处示例是在前文例子的基础上稍做了些修改。
1)定义一个接口
修改接口,加上了达博的@SPI注解。
<代码> @SPI 公共接口操作{ ,,,int, int操作(int num1 num2); }代码>
2)写两个简单的实现
沿用之前的两个实现类。
3)添加一个配置文件
新增配置文件放在达博目录下。
目录结构
文件内容
<代码>=com.api.impl.DivisionOperation分工 +=com.api.impl。PlusOperation 代码>
4)测试程序
<代码>公共类DubboSpiTest { ,,@Test ,,公共空testOperation()抛出异常{ ,,,,,ExtensionLoader装载机=ExtensionLoader.getExtensionLoader (Operation.class); ,,,,,业务司=loader.getExtension (“division"); ,,,,,System.out.println(结果:“;+部门。操作(1、2)); ,,} }代码>
5)测试结果
<代码>运行部门操作 null null达博SPI扩展类的加载过程