SPI(Service Provider Interface)详解
介绍
SPI 全称为 (Service Provider Interface) ,是JVM内置的一种服务提供发现机制。Java在语言层面为我们提供了一种方便地创建可扩展应用的途径。我们只需要按照SPI的要求,在jar包中进行适当的配置,jvm就会在运行时通过懒加载,帮我们找到所需的服务并加载。如果我们一直不使用某个服务,那么它不会被加载,一定程度上避免了资源的浪费。
1、应用
熟悉JDBC的同学都知道,在jdbc4.0之前,在使用DriverManager获取DB连接之前,我们总是需要通过Class.forName显示的加载驱动(为了执行驱动类的static代码,注册驱动实例对象到DriverManager中),例如:
Connection comm = null;
Statement stmt = null;
try {//注册mysql的jdbc驱动Class.forName("com.mysql.jdbc.Driver");//创建连接conn = DriverManagger.getConnection(url,user,pwd);
}
在JDBC4.0开始,这个显式的初始化不再是必选项了,它存在的意义只是为了向上兼容。那么JDBC4.0之后,我们的应用是如何找到对应的驱动呢?答案就是SPI。
2、使用
- 一个服务(Service)通常指的是已知的接口或者抽象类(SPI并没有强制要求服务必须是interface或abstract class,完全可以将class注册为SPI注册服务)
- 服务提供方就是对这个接口或者抽象类的实现,按照SPI 标准,在资源路径META-INF/services目录下创建一个文件(文件的命名为该服务接口的全限定名);内容为实现类的全限定名,每行一个,可以使用#作为注释
- 将服务的提供代码打包成jar,放到classpath下;
完成以上后,在程序运行时可使用ServiceLoader.load(Class class) api获取到接口服务的所有实现类。SPI底层原理:使用懒加载的方式,在运行时将该服务接口的实现类通过Class.forName的方式加载到JVM中,并做实例初始化。
示例
1)接口服务:
package spi;public interface Animal {void eat();void sleep();
}
2)服务实现:
package spi;public class Dog implements Animal{@Overridepublic void eat() {System.out.println("dog eat...");}@Overridepublic void sleep() {System.out.println("dog sleep...");}
}
3)SPI配置文件:
在resource下创建META-INf/services目录,然后创建文件spi.Animal,内容:spi.Dog
4)测试代码:
package spi;import java.util.Iterator;
import java.util.ServiceLoader;
public class SPITest {public static void main(String[] args) {ServiceLoader<Animal> animals = ServiceLoader.load(Animal.class);for (Animal animal : animals) {//增强型for循环等价于下面的iterator迭代animal.eat();animal.sleep();}Iterator<Animal> iterator = animals.iterator();while(iterator.hasNext()) {Animal animal = iterator.next();animal.eat();animal.sleep();}}
}
说明:本示例是在一个工程下演示的,可以将Animal接口打包成animal.jar,Dog实现工程中实现引入animal.jar,并且配置META-INF文件,在打包成dog.jar。最后创建一个test工程,引入dog.jar即可进行测试。
源码解读
1、创建ServiceLoader实例:
public final class ServiceLoader<S> implements Iterable<S> {//...//重新load指定serivice的实现。通过LazyIterator实现懒加载。public void reload() {providers.clear();//是个LinkedHashMap 类型lookupIterator = new LazyIterator(service, loader);}//私有ServiceLoader构造函数,须通过ServiceLoader.load(Class<?>)静态方法创建ServiceLoader实例private ServiceLoader(Class<S> svc, ClassLoader cl) {service = Objects.requireNonNull(svc, "Service interface cannot be null");loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;reload();}//构建ServiceLoader实例public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){return new ServiceLoader<>(service, loader);}//通过service的class创建ServiceLoader实例,默认使用上下文classloaderpublic static <S> ServiceLoader<S> load(Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);}
}
说明:
- 通过load静态方法创建ServiceLoader实例,构造完ServiceLoader后,并不会立刻扫描当前进程中的服务实现,而是创建一个LazyIterator懒加载迭代器,在实际使用时采取扫描服务实现类并加载;
- 默认使用当前线程的上下文classLoader,后面会用该classLoader加载服务实现类;
2、ServiceLoader的遍历:
ServiceLoader实现Iterable<?>接口,调用load方法(创建ServiceLoader实例)返回了一个服务类型的迭代器,接下来使用iterator(或者增强型for循环)遍历ServiceLoader实例时,才会真正的扫描、加载对应的服务实现类。
public final class ServiceLoader<S> implements Iterable<S> {//...//缓存的service provider,按照初始化顺序排列。private LinkedHashMap<String,S> providers = new LinkedHashMap<>();//当前的LazyIterator迭代器指针,服务懒加载迭代器private LazyIterator lookupIterator;//增强型for循环或者直接iterator()方法遍历ServiceLoaderpublic Iterator<S> iterator() {return new Iterator<S>() {//创建Iterator迭代器时的ServiceLoader.providers快照,//因此在首次迭代时,iterator总是会通过LazyIterator进行懒加载Iterator<Map.Entry<String,S>> knownProviders= providers.entrySet().iterator();public boolean hasNext() {// 如果已经扫描过,则对providers进行迭代;if (knownProviders.hasNext())return true;// 如果没有扫描过,则通过lookupIterator进行扫描和懒加载return lookupIterator.hasNext();}public S next() {// 如果已经扫描过,则对providers进行迭代;if (knownProviders.hasNext())return knownProviders.next().getValue();// 如果没有扫描过,则通过lookupIterator进行扫描和懒加载return lookupIterator.next();}public void remove() {throw new UnsupportedOperationException();}};}
}
说明:
- 首次迭代时,因为ServiceLoader.providers中没有任何缓存,总是会通过LazyIterator进行懒加载,并将service实现的全限定名与加载的service实例作为key-value缓存到ServiceLoader.providers中。
- 之后再进行迭代时,总是在ServiceLoader.providers中进行。
3、懒加载迭代器LazyIterator:
public final class ServiceLoader<S> implements Iterable<S> {private static final String PREFIX = "META-INF/services/";//...private class LazyIterator implements Iterator<S> {//内部类Class<S> service;ClassLoader loader;Enumeration<URL> configs = null;Iterator<String> pending = null;//配置文件中的服务实现类的迭代器(每行一个)String nextName = null;private LazyIterator(Class<S> service, ClassLoader loader) {this.service = service; this.loader = loader;}private boolean hasNextService() {if (nextName != null) {return true;}if (configs == null) {//首次迭代时try {String fullName = PREFIX + service.getName();if (loader == null)//通过ClassLoader.getResources()获得资源URL集合configs = ClassLoader.getSystemResources(fullName);elseconfigs = loader.getResources(fullName);} catch (IOException x) {fail(service, "Error locating configuration files", x);}}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;try {Class<?> c = Class.forName(cn, false, loader);S p = service.cast(c.newInstance());providers.put(cn, p);return p;} catch (ClassNotFoundException x) {fail(service,"Provider " + cn + " not found");}throw new Error(); // This cannot happen}public boolean hasNext() {if (acc == null) {return hasNextService();} else {PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {public Boolean run() { return hasNextService(); }};return AccessController.doPrivileged(action, acc);}}public S next() {if (acc == null) {return nextService();} else {PrivilegedAction<S> action = new PrivilegedAction<S>() {public S run() { return nextService(); }};return AccessController.doPrivileged(action, acc);}}public void remove() {throw new UnsupportedOperationException();}}
}
说明:
- hasNextService方法实现了资源的查找,通过classLoader的getResources方法实现;
- nextService方法实现了服务实现类的实例化,通过Class.forName实现;
- LazyIterator中除了hasNextService、nextService方法外还有Iterator的hasNext、next方法,其内部调用了hasNextService和nextService。
4、其他:
ServiceLoader类并不复杂,实现了SPI的所有逻辑,内部出了上面说的一些方法外,还有parse及parseLine的代码,可以发现,parseLine中会对服务实现类进行去重,所以在一个或多个services配置文件中配置多次的服务实现类只会被处理一次。
应用
1、jdbc:
在https://blog.csdn.net/liuxiao723846/article/details/112397128 已经介绍过一次jdbc驱动的加载过程,这里再简单重复一下。
DriverManager的static代码块中会执行loadInitialDrivers方法,该方法内部首先通过jdbc.drivers配置加载驱动,然后会通过SPI来加载驱动,如下:
在mysql驱动库中实现了SPI标准,并且在static代码块中创建驱动实例,注册到jdbc的DriverManager中。接下来,就可以通过DriverManager来创建connection了。
2、slf4j
参考:
https://www.jianshu.com/p/27c837293aeb
https://linxiaobai.github.io/2018/05/18/ClassLoader%E5%8E%9F%E7%90%86%E4%BB%A5%E5%8F%8ASPI/
SPI(Service Provider Interface)详解相关推荐
- Java中的SPI(Service Provider Interface介绍及示例
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 一个服务 ...
- Java基础学习总结(145)——Java SPI(Service Provider Interface)简介
SPI 简介 SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.一个服务(Service)通常指的是已知的接口或者抽象类,服务提供方就是对 ...
- java provider_Java SPI(Service Provider Interface)
//ServiceLoader实现了Iterable接口,可以遍历所有的服务实现者 public final class ServiceLoader implements Iterable{//查找配 ...
- SPI : Service Provider Interface
1.概述 jdk6中引入了一个新特性,使得我们可以根据一个指定的「接口」去找到并加载指定的「实现」. 本篇文章会详细介绍下SPI的用法及场景. 2.关键组成 以java.sql.Driver为例 Se ...
- Laravel Service Provider 概念详解
https://learnku.com/articles/6189/laravel-service-provider-detailed-concept-收藏一下 我们知道, Container 有很多 ...
- SPI(Service Provider Interface)
ServiceLoad中的spi 1.简介 JDK1.6引入的特性,用来实现SPI(Service Provider Interface),一种服务发现机制. 2.JDBC举例 2.1.引入mysql ...
- Service Provider Interface(SPI)
目录 1.什么是SPI 2.SPI的使用 3.源码分析: 4.应用 1.什么是SPI SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口 ...
- @Controller,@Service,@Repository,@Component详解
转载自 @Controller,@Service,@Repository,@Component详解 @Controller 用来表示一个web控制层bean,如SpringMvc中的控制器. @Ser ...
- Android四大组件Service之AIDL详解
Android四大组件Service之AIDL详解 前言 简介 基础知识 AIDL 服务端 定义AIDL文件规则 创建 .aidl 文件 清单注册 通过 IPC 传递对象 调用 IPC 方法 Andr ...
最新文章
- 将DataTable 数据插入 SQL SERVER 数据库
- (待定系数法)A/B
- SLAM学习策略和前期准备
- 假设以带头结点的循环链表表示队列_数据结构·链表(C实现)
- Java 8 函数接口详细教程
- 全球首发!惯性导航导论(剑桥大学)第四部分
- 计蒜客 - T1012 A*B问题
- WebStorm破解---最新 2019.4.24
- (转)IE劫持原理 BHO
- 超详细SPSS主成分分析计算指标权重(二:权重计算及极差法标准化)
- 租住南山的互联网大厂人:年入70万,睡城中村单间
- 猪八戒网冲刺港交所:朱明跃已奋斗16年 年营收7.68亿
- Oracle中文排序 NLSSORT
- fixed income
- v2rayN断网修复
- linux32 浏览器,谷歌停止支持32位linux系统的Chrome浏览器
- 5g网站服务器宽带,别装有线宽带了,5G以后,有线宽带将被淘汰
- Zookeeper 原理与优化
- gpu 虚拟服务器玩游戏,gpu云服务器可以玩游戏吗
- OpenGL 图形库的使用(二十五)—— 高级OpenGL之帧缓冲Framebuffers
热门文章
- 【感悟】2019/3/11
- canape调用map文件选择的格式
- 打击犯罪(black)
- python - 猜单词游戏
- ksrot php_JS实现PHP ksort方法
- 《复联4》中这位科学家的理论是时间穿越关键!钢铁侠差点因此拒绝出手 | 剧透预警...
- 51单片机—详细(存储器RAM/ROM、引脚、中断系统、定时/计数器、串行口通信)
- 疯狂Java讲义-面向对象(下)
- 《土力学原理十记》笔记++
- Choosing Quadcopter Motors and Props