概述
SPI服务扩展机制
SPI全称为service-provider,中文意思是服务扩展机制。
SPI:是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
也可以这样理解:SPI是“基于接口的编程+策略模式+配置文件”组成实现的动态加载机制。
可能比较抽象,等看完就能理解了。
使用到SPI的例子:
- Servlet规范
Servlet3.0规范启动ServletContainerInitializer启动流程,在Servlet中就使用到了SPI,servlet-api Jar只是定义了一些规范。服务的具体实现由具体的Web容器(Tomcat / Jboss / Jetty)去实现。 - JDBC规范
JDBC规范也是只定义了一些接口和类,具体服务的实现由具体的数据库厂商(mysql驱动 / Oracle驱动)去实现。
1. 当服务提供者提供了接口的一种具体实现后,在工程中的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名。
2. 接口实现类所在的工程classpath中:
3. 主程序通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名。
4. SPI的实现类必须具有一个不带参数的构造方法
下面举个例子:
定义了一个文件上传的接口
package com.zlin.spi;
/**
* SPI
* 上传文件接口 binary name
*/
public interface IUpload {
void upload();
}
然后Img PDF Txt文件的实现类都重写upload方法
package com.zlin.spi;
public class ImgUpload implements IUpload{
@Override
public void upload() {
System.out.println("上传图片");
}
}
package com.zlin.spi;
public class PdfUpload implements IUpload{
@Override
public void upload() {
System.out.println("上传PDF");
}
}
package com.zlin.spi;
public class TxtUpload implements IUpload{
@Override
public void upload() {
System.out.println("上传txt文件");
}
}
然后在工程中的META-INF/services目录下创建一个以“接口全限定名”为命名的文件,内容为实现类的全限定名。
再通过java.util.ServiceLoader动态装载实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名。
先看效果
去查看ServiceLoader的源码会发现,
ServiceLoader<IUpload> iUploads = ServiceLoader.load(IUpload.class);
会去获取线程上下文类加载器,然后把我们的接口类型保存到ServiceLoader变量中,最后创建一个懒加载的迭代器。
返回我们懒加载的迭代器对象
Iterator<IUpload> iUploadIterator = iUploads.iterator();
iUploadIterator.hasNext():会读取"META-INF/services/com.zlin.spi.IUpload"的内容,然后把文件中的第一个值(第一个实现类的全限定名)保存到nextName变量。
第二次把第二个值保存到newxtName变量(第二个实现类的全限定名)
依次下去…
iUploadIterator.hasNext():
iUploadIterator.next() 得到的是接口的实现类的实例, 会调用 c = Class.forName(cn, false, loader);
iUploadIterator.next().upload();
hasNext()和next()的源码这里就不贴上来了,因为很简单也很好理解。
要是真的想学习的小伙伴,建议去自己动手敲一遍,进到hasNext()和next()里面打断点,整个流程走一遍,就全都懂了。
package com.zlin.spi;
import java.util.Iterator;
import java.util.ServiceLoader;
public class SpiTest {
public static void main(String[] args) {
testSpi();
}
private static void testSpi() {
// 第一步:把我们的接口类型保存到ServiceLoader变量中
// 第二步:创建一个我们 懒加载的迭代器
// 1.保存了IUpload接口到service 2.保存线程上下文类加载器(AppClassLoader) 3.创建一个懒加载的迭代器LazyIterator
ServiceLoader<IUpload> iUploads = ServiceLoader.load(IUpload.class);
// 返回我们懒加载的迭代器对象
Iterator<IUpload> iUploadIterator = iUploads.iterator();
// iUploadIterator.hasNext(): 读取"META-INF/services/接口全类名"的内容
// 第一次把"META-INF/services/接口全类名"文件中的第一个值保存到newxtName变量 (第一个实现类的全限定名)
// 第二次把第二个值保存到newxtName变量(第二个实现类的全限定名) 依次下去...
while (iUploadIterator.hasNext()) {
// iUploadIterator.next() 得到的是接口的实现类的实例
// iUploadIterator.next() 会调用 c = Class.forName(cn, false, loader);
// 会触发类的初始化 ==> 调用类的静态代码块 初始化只会触发一次
iUploadIterator.next().upload();
}
}
}
SPI在mysql-connector-java中的使用:
上面涉及到的代码地址:
https://github.com/zhonglinliu123/MyBlogCode/tree/master/java/SPI
最后
以上就是爱听歌板栗为你收集整理的SPI服务扩展机制的全部内容,希望文章能够帮你解决SPI服务扩展机制所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复