1.spi 是什么?
spi 的全称是 service provider interface, 即提供服务接口;是一种服务发现机制,spi 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 spi 机制为我们的程序提供拓展功能。 如下图:
系统设计的各个抽象,往往有很多不同的实现方案,在面对象设计里,一般推荐模块之间基于接口编程,模块之间不对实现硬编码,一旦代码涉及具体的实现类,就违反了可插拔的原则。java spi 就是提供这样的一个机制,为某一个接口寻找服务的实现,有点类似 ioc 的思想,把装配的控制权移到程序之外,在模块化涉及里面这个各尤为重要。与其说 spi 是 java 提供的一种服务发现机制,倒不如说是一种解耦思想。
2. 使用场景
数据库驱动加载接口实现类的加载;如:jdbc 加载 mysql,oracle...
日志门面接口实现类加载,如:slf4j 对 log4j、logback 的支持
spring 中大量使用了 spi,特别是 spring-boot 中自动化配置的实现
dubbo 也是大量使用 spi 的方式实现框架的扩展,它是对原生的 spi 做了封装,允许用户扩展实现 filter 接口。
3. 使用介绍
要使用 java spi,需要遵循以下约定:
当服务提供者提供了接口的一种具体实现后,需要在 jar 包的 meta-inf/services 目录下创建一个以 “接口全限制定名” 为命名的文件,内容为实现类的全限定名;
接口实现类所在的 jar 放在主程序的 classpath 下,也就是引入依赖。
主程序通过 java.util.serviceloder 动态加载实现模块,它会通过扫描 meta-inf/services 目录下的文件找到实现类的全限定名,把类加载值 jvm, 并实例化它;
spi 的实现类必须携带一个不带参数的构造方法。
示例:
spi-interface 模块定义
定义一组接口:public interface mydriver
spi-jd-driver
spi-ali-driver
实现为:public class jddriver implements mydriver public class alidriver implements mydriver
在 src/main/resources/ 下建立 /meta-inf/services 目录, 新增一个以接口命名的文件 (org.mydriver 文件)
内容是要应用的实现类分别 com.jd.jddriver 和 com.ali.alidriver
spi-core
一般都是平台提供的核心包,包含加载使用实现类的策略等等,我们这边就简单实现一下逻辑:a. 没有找到具体实现抛出异常 b. 如果发现多个实现,分别打印
public void invoker(){ serviceloader serviceloader = serviceloader.load(mydriver.class); iterator drivers = serviceloader.iterator(); boolean isnotfound = true; while (drivers.hasnext()){ isnotfound = false; drivers.next().load(); } if(isnotfound){ throw new runtimeexception(一个驱动实现类都不存在); }}
spi-test
public class app { public static void main( string[] args ) { driverfactory factory = new driverfactory(); factory.invoker(); }}
1. 引入 spi-core 包,执行结果
2. 引入 spi-core,spi-jd-driver 包
3. 引入 spi-core,spi-jd-driver,spi-ali-driver
4. 原理解析
看看我们刚刚是怎么拿到具体的实现类的? 就两行代码:
serviceloader serviceloader = serviceloader.load(mydriver.class);iterator drivers = serviceloader.iterator();
所以,首先我们看 serviceloader 类:
public final class serviceloader implements iterable{//配置文件的路径 private static final string prefix = meta-inf/services/; // 代表被加载的类或者接口 private final class service; // 用于定位,加载和实例化providers的类加载器 private final classloader loader; // 创建serviceloader时采用的访问控制上下文 private final accesscontrolcontext acc; // 缓存providers,按实例化的顺序排列 private linkedhashmap providers = new linkedhashmap(); // 懒查找迭代器,真正加载服务的类 private lazyiterator lookupiterator; //服务提供者查找的迭代器 private class lazyiterator implements iterator { .....private boolean hasnextservice() { if (nextname != null) { return true; } if (configs == null) { try {//全限定名:com.xxxx.xxx string fullname = prefix + service.getname(); if (loader == null) configs = classloader.getsystemresources(fullname); else configs = loader.getresources(fullname); } } while ((pending == null) || !pending.hasnext()) { if (!configs.hasmoreelements()) { return false; } pending = parse(service, configs.nextelement()); } nextname = pending.next(); return true; } private s nextservice() { if (!hasnextservice()) throw new nosuchelementexception(); string cn = nextname; nextname = null; class c = null; try {//通过反射获取 c = class.forname(cn, false, loader); } if (!service.isassignablefrom(c)) { fail(service, provider + cn + not a subtype); } try { s p = service.cast(c.newinstance()); providers.put(cn, p); return p; } }........
大概的流程就是下面这张图:
应用程序调用 serviceloader.load 方法
应用程序通过迭代器获取对象实例,会先判断 providers 对象中是否已经有缓存的示例对象,如果存在直接返回
如果没有存在,执行类转载读取 meta-inf/services 下的配置文件,获取所有能被实例化的类的名称,可以跨越 jar 获取配置文件通过反射方法 class.forname () 加载对象并用 instance () 方法示例化类将实例化类缓存至 providers 对象中,同步返回。
5. 总结
优点:解耦
spi 的使用,使得第三方服务模块的装配控制逻辑与调用者的业务代码分离,不会耦合在一起,应用程序可以根据实际业务情况来启用框架扩展和替换框架组件。
spi 的使用,使得无须通过下面几种方式获取实现类
代码硬编码 import 导入
指定类全限定名反射获取,例如 jdbc4.0 之前;class.forname(com.mysql.jdbc.driver)
缺点:
虽然 serviceloader 也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过 iterator 形式获取,不能根据某个参数来获取对应的实现类。
6. 对比
虹科方案 | 不能连网的设备也能远程控制和设备上云?虹科教您使用UBIQUITY远程协助路由器轻松实现!
始于颜值 忠于实力 ROCCAT冰豹新品三件套开箱体验
PIC单片机ADC的编程设计
家用空气能热水器在售后中出现E1高压保护怎么办
iPhone XR的相机拥有你可能希望拥有的所有新特性
可插拔组件设计机制—SPI介绍
ASRS(自动存储和检索系统)在内部物流领域的应用
固定式读卡器实现无人接触管理,推动生产效率
中美半导体产业技术和贸易限制工作组成立,对中国半导体行业有重要意义
智能扫地机器人好用吗?好用的扫地机器人都具有这几点
探讨现代物流进行深度融合的零售新模式
区块链技术在金融领域有着非常可期的市场潜力
日本DISCO晶圆设备推新:SiC切割设备面世
配电监控系统的功能特点
赛昉科技与港华智慧能源、名气家签署“港华芯”工业系列深度合作推广协议
向世界展示“中国品牌”实力,中海达参展INTERGEO
PayPal帐户链接到Facebook和Messenger
力拼小米6,一加4能拿下高通骁龙835的首发权吗?
电子工程师的内心独白
多维科技推出面向工业、医疗和汽车应用的升级版 TMR 磁开关传感器