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()都会返回一个文件中定一个类的实例化对象,有了实例化对象,那岂不是可以为所欲为了。