介绍

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)详解相关推荐

  1. Java中的SPI(Service Provider Interface介绍及示例

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 一个服务 ...

  2. Java基础学习总结(145)——Java SPI(Service Provider Interface)简介

    SPI 简介 SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.一个服务(Service)通常指的是已知的接口或者抽象类,服务提供方就是对 ...

  3. java provider_Java SPI(Service Provider Interface)

    //ServiceLoader实现了Iterable接口,可以遍历所有的服务实现者 public final class ServiceLoader implements Iterable{//查找配 ...

  4. SPI : Service Provider Interface

    1.概述 jdk6中引入了一个新特性,使得我们可以根据一个指定的「接口」去找到并加载指定的「实现」. 本篇文章会详细介绍下SPI的用法及场景. 2.关键组成 以java.sql.Driver为例 Se ...

  5. Laravel Service Provider 概念详解

    https://learnku.com/articles/6189/laravel-service-provider-detailed-concept-收藏一下 我们知道, Container 有很多 ...

  6. SPI(Service Provider Interface)

    ServiceLoad中的spi 1.简介 JDK1.6引入的特性,用来实现SPI(Service Provider Interface),一种服务发现机制. 2.JDBC举例 2.1.引入mysql ...

  7. Service Provider Interface(SPI)

    目录 1.什么是SPI 2.SPI的使用 3.源码分析: 4.应用 1.什么是SPI SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的接口 ...

  8. @Controller,@Service,@Repository,@Component详解

    转载自 @Controller,@Service,@Repository,@Component详解 @Controller 用来表示一个web控制层bean,如SpringMvc中的控制器. @Ser ...

  9. Android四大组件Service之AIDL详解

    Android四大组件Service之AIDL详解 前言 简介 基础知识 AIDL 服务端 定义AIDL文件规则 创建 .aidl 文件 清单注册 通过 IPC 传递对象 调用 IPC 方法 Andr ...

最新文章

  1. 将DataTable 数据插入 SQL SERVER 数据库
  2. (待定系数法)A/B
  3. SLAM学习策略和前期准备
  4. 假设以带头结点的循环链表表示队列_数据结构·链表(C实现)
  5. Java 8 函数接口详细教程
  6. 全球首发!惯性导航导论(剑桥大学)第四部分
  7. 计蒜客 - T1012 A*B问题
  8. WebStorm破解---最新 2019.4.24
  9. (转)IE劫持原理 BHO
  10. 超详细SPSS主成分分析计算指标权重(二:权重计算及极差法标准化)
  11. 租住南山的互联网大厂人:年入70万,睡城中村单间
  12. 猪八戒网冲刺港交所:朱明跃已奋斗16年 年营收7.68亿
  13. Oracle中文排序 NLSSORT
  14. fixed income
  15. v2rayN断网修复
  16. linux32 浏览器,谷歌停止支持32位linux系统的Chrome浏览器
  17. 5g网站服务器宽带,别装有线宽带了,5G以后,有线宽带将被淘汰
  18. Zookeeper 原理与优化
  19. gpu 虚拟服务器玩游戏,gpu云服务器可以玩游戏吗
  20. OpenGL 图形库的使用(二十五)—— 高级OpenGL之帧缓冲Framebuffers

热门文章

  1. 【感悟】2019/3/11
  2. canape调用map文件选择的格式
  3. 打击犯罪(black)
  4. python - 猜单词游戏
  5. ksrot php_JS实现PHP ksort方法
  6. 《复联4》中这位科学家的理论是时间穿越关键!钢铁侠差点因此拒绝出手 | 剧透预警...
  7. 51单片机—详细(存储器RAM/ROM、引脚、中断系统、定时/计数器、串行口通信)
  8. 疯狂Java讲义-面向对象(下)
  9. 《土力学原理十记》笔记++
  10. Choosing Quadcopter Motors and Props