网站首页 > 技术文章 正文
java篇之SPI详解
Q SPI能做什么?
请先看下面一个业务场景:
框架开发人者-小框:我要开发一款发送消息的框架,为业界的消息发送定义一个标准,具体的发送内容需要交给框架的实现者去做 (类似jdbc,是一个连接数据库的标准)
中间件开发者-小中:最近这款消息框架使用的挺火的,公司要求依据这个框架开发一款发送消息的中间件 (类似mysql的driver,实现如何具体的连接mysql数据库)
后台开发人员-小开:来测试一下公司的消息中间件看看有没有bug (日常的开发,使用jdbc去做mysql数据库连接)
具体实现:
小框:
1、新建maven项目 tool
2、编写接口Msg,定义发送消息的方法
3、 编写发送接口类SendMsg:
好了,剩余的交给实现人员去做
小中:
1、新建maven项目 toolRealize
2、编写实现类 MsgRealize ,做发送消息的具体实现
3、resources下新建目录 META-INF/services,新建文件 com.spi.tool.Msg,文件中写入内容com.coder.toolRealize. MsgRealize
好了,打包上传maven仓库,等别的开发调用
小开:
1、新建maven项目 testSpi
2、引入tool 和 toolRealize
3、新建main方法,测试消息发送
完成!
经过上面的测试,大致能了解SPI能做什么,在我的理解看来,SPI是一种可拔插的实现机制,也类似于策略模式,可以在项目中根据加载不同的jar来动态的实现某些业务。
Q SPI是什么?
SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
02
源码分析
基于JDK1.8分析 ServiceLoader
1、整体分析ServiceLoader的属性
//PREFIX属性代表着我们必须要把实现类的配置文件写在services目录下,属于硬编码内容
private static final String PREFIX = "META-INF/services";
//需要ServiceLoader加载的类的Class对象
private final Class<S> service;
//类加载器,加载配置类
private final ClassLoader loader;
//已经加载的Class的缓存,避免每次都去读配置文件加载
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
//重写了java迭代器,类加载的过程以及实例化都是迭代器完成的,
private LazyIterator lookupIterator;
2、根据调用过程逐步分析源码的执行思路
a)、实际开发中使用SPI的调用流程如下:
ServiceLoader.load(obj.class) // 第一步
.iterator() // 第二步
.hasNext() // 第三步
.next(); // 第四步
b)、思路解析
第一步中执行过程是获取当前类加载器->执行ServiceLoader构造方法->执行reload()方法->清空缓存、实例化迭代器;
第二步获取当前的迭代器对象;
第三步实际执行的是LazyIterator.hasNext() ,然后调用-> hasNextService()
private boolean hasNextService() {
....//省略部分不关键代码
//拼接路径前缀,获取文件的全路径名
String fullName = PREFIX + service.getName();
//读取文件中的所有内容
config = loader.getResources(fullName);
//剩余代码主要就是把文件中写的所有实现类都放置在一个内置的迭代器中,
//防止每次next都去取文件,保证了文件只会最开始读取一次
...//省略部分不关键代码
}
第四步实际执行的是LazyIterator.next(),然后调用 ->nextService()
private S nextService() {
... //省略部分不关键代码
String cn = nextName; //获取当前实现类的全类名,如com.a5.User
nextName = null; //清空当前属性,可以在hasNextService中查询下一个实现类的全类名
Class<?> c = null;
c = Class.forName(cn,false,loader); //根据全类名获取Class对象
... //省略部分不关键代码
S p = service.cast(c.newInstance());//newInstance获取当前类的实例
proproviders.put(cn,p); //放入缓存
return p;//返回实例对象,方法结束
}
3、解析过源码之后我们发现,每次next()都会返回一个文件中定一个类的实例化对象,有了实例化对象,那岂不是可以为所欲为了。
猜你喜欢
- 2024-12-18 Java开发中MongoDB数据库相关操作
- 2024-12-18 HashMap有几种遍历方法?推荐使用哪种?
- 2024-12-18 在RedisTemplate中使用scan代替keys指令
- 2024-12-18 MQ的发布订阅模式(fanout) mq几种消息模式
- 2024-12-18 Mybatis参数-ParameterMapping处理参数
- 2024-12-18 既然有MySQL了,为什么还要有MongoDB?
- 2024-12-18 Java遍历一个 List 有哪些方式?每种的实现原理以及哪种最高效?
- 2024-12-18 为什么很多人不愿意用hibernate了?
- 2024-12-18 Qt中 QMap 类、QHash 类、QVector 类详解
- 2024-12-18 半小时搞懂 IO 模型 io模型详解
- 1507℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 512℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 487℃MySQL service启动脚本浅析(r12笔记第59天)
- 467℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 465℃启用MySQL查询缓存(mysql8.0查询缓存)
- 445℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 424℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 422℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)