您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

SPI是什么?一起来看看

时间:2019-06-19 15:17:53  来源:  作者:

听过了API,那SPI是什么?一起来看看

 

引语

平时API倒是听得很多?SPI又是啥.别急我们来先看看面向接口编程的调用关系,来了解一下,API和SPI的相似和不同之处。

SPI理解

先来一段官话的介绍:SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.(听了一脸懵逼)好的,我们结合图片来理解一下。

听过了API,那SPI是什么?一起来看看

 

简单的来说分为调用方,接口,服务方.接口就是协议,契约,可以调用方定义,也可以由服务方定义,也就是接口是可以位于调用方的包或者服务方的包. 1.接口的定义和实现都在服务方的时候,仅暴露出接口给调用方使用的时候,我们称为API; 2.接口的定义在调用方的时候(实现在服务方),我们给它也取个名字--SPI。 应该还比较好理解吧?

SPI的使用场景

SPI在框架中其实有很多广泛的应用,这里列举几个例子: 1.MySQL驱动的选择driverManager根据配置来确定要使用的驱动;

2.dubbo框架中的扩展机制

使用实例

看完上面的简介和SPI在框架中的应用,想必对SPI在读者的大脑中已经产生了一个雏形,talk is cheap!show me the code.说了这么多,我们具体写一个简单的例子来看看效果,验证一下SPI.

1.首先定义一个接口,忍者服务接口

public interface NinjaService {
 void performTask();
}

2.接下来写两个实现类,ForbearanceServiceImpl(上忍),ShinobuServiceImpl(下忍)

public class ForbearanceServiceImpl implements NinjaService {
 @Override
 public void performTask() {
 System.out.println("上忍在执行A级任务");
 }
}
public class ShinobuServiceImpl implements NinjaService {
 @Override
 public void performTask() {
 System.out.println("下忍在执行D级任务");
 }
}

3.接下来我们在main/resources/下创建META-INF/services目录,并且在services目录下创建一个com.scott.JAVA.task.spi.NinjaService(忍者服务类的全限定名)的文件.

4.创建一个Client场景类来调用看看结果

听过了API,那SPI是什么?一起来看看

 

很完美的调用了两个实现类的performTask()方法.

5.最后贴一下目录结构

听过了API,那SPI是什么?一起来看看

 

SPI源码简单分析

1.先看下核心类ServiceLoader的定义和属性

// 继承了Iterable类 遍历的时候使用
public final class ServiceLoader<S> implements Iterable<S>
{
 // 这就是为啥需要在META-INF/services/目录下创建服务类的文件
 private static final String PREFIX = "META-INF/services/";
 // 被加载的服务
 private final Class<S> service;
 // 类加载器
 private final ClassLoader loader;
 // 访问控制类
 private final AccessControlContext acc;
 // 实现类的缓存 根据初始化的顺序 也就是在/services/文件中的定义顺序来定义的加载顺序
 private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
 // 懒加载iterator
 private LazyIterator lookupIterator;

2.然后从client开始,然后依次debug进去

ServiceLoader<NinjaService> ninjaServices = ServiceLoader.load(NinjaService.class);

 
public static <S> ServiceLoader<S> load(Class<S> service) {
 // 获取当前的类加载器 也就是AppClassLoader
 ClassLoader cl = Thread.currentThread().getContextClassLoader();
 return ServiceLoader.load(service, cl);
 }
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); }

后面的就省略了,因为这里仅仅就是根据NinjaService初始化的事项,没有什么很难理解的点.

3.我们在看看具体的调用过程,这里使用的是client对应的class文件,因为增加for(foreach)在java中是个语法糖,实际上编译后是这样的内容

public static void main(String[] args) {
 ServiceLoader<NinjaService> ninjaServices = ServiceLoader.load(NinjaService.class);
 // 这里一下其实就是增加for解糖后的代码 有兴趣可以去了解下java的语法糖
 Iterator var2 = ninjaServices.iterator();
 while(var2.hasNext()) {
 NinjaService item = (NinjaService)var2.next();
 item.performTask();
 }
 }

4.随着断点继续走,我们进入到var2.hasNext()的方法

public boolean hasNext() {
 // knownProviders还没有加载过provider 走下面的分支
 if (knownProviders.hasNext())
 return true;
 return lookupIterator.hasNext();
 }

这里lookupIterator上面ServiceLoader的属性介绍过,它其实是ServiceLoader中的一个Iterator的内部类。然后调用了内部类Iterator的hasNext()方法。

public boolean hasNext() {
 // acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
 // ServiceLoader初始化没有设置过securityManager,所以acc是null,进入hasNextService()
 if (acc == null) {
 return hasNextService();
 } else {
 PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
 public Boolean run() { return hasNextService(); }
 };
 return AccessController.doPrivileged(action, acc);
 }
 }

5.hasNextService()分析

private boolean hasNextService() {
 if (nextName != null) {
 return true;
 }
 if (configs == null) {
 try {
 // 这里加载了META-INF/services下的文件 也就是含有两个实现类全限定名的文件
 String fullName = PREFIX + service.getName();
 if (loader == null)
 configs = ClassLoader.getSystemResources(fullName);
 else
 // 因为loader是不为null 的AppClassLoader
 configs = 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;
 }

6.继续看到parse方法,这里最后返回的是含有两个全限定类名的Iterator,其实就是把services/下的文件内容给加载出来

private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError {
 InputStream in = null;
 BufferedReader r = null;
 ArrayList<String> names = new ArrayList<>();
 try {
 in = u.openStream();
 r = new BufferedReader(new InputStreamReader(in, "utf-8"));
 int lc = 1;
 while ((lc = parseLine(service, u, r, lc, names)) >= 0);
 } catch (IOException x) {
 fail(service, "Error reading configuration file", x);
 } finally {
 try {
 if (r != null) r.close();
 if (in != null) in.close();
 } catch (IOException y) {
 fail(service, "Error closing configuration file", y);
 }
 }
 return names.iterator();
}

附带的说一下parseLine(service, u, r, lc, names),检查类名是否符合规范,符合的话添加到Iterator中,到这里var2 .hasNext()执行完毕,结果是加载了services下的文件内容

private int parseLine(Class<?> service, URL u, BufferedReader r, int lc,
 List<String> names)
 throws IOException, ServiceConfigurationError
 {
 String ln = r.readLine();
 if (ln == null) {
 return -1;
 }
 int ci = ln.indexOf('#');
 if (ci >= 0) ln = ln.substring(0, ci);
 ln = ln.trim();
 int n = ln.length();
 if (n != 0) {
 if ((ln.indexOf(' ') >= 0) || (ln.indexOf('	') >= 0))
 fail(service, u, lc, "Illegal configuration-file syntax");
 int cp = ln.codePointAt(0);
 if (!Character.isJavaIdentifierStart(cp))
 fail(service, u, lc, "Illegal provider-class name: " + ln);
 for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
 cp = ln.codePointAt(i);
 if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
 fail(service, u, lc, "Illegal provider-class name: " + ln);
 }
 if (!providers.containsKey(ln) && !names.contains(ln))
 names.add(ln);
 }
 return lc + 1;
 }
private boolean hasNextService() {
 if (nextName != null) {
 return true;
 }
 if (configs == null) {
 try {
 String fullName = PREFIX + service.getName();
 if (loader == null)
 configs = ClassLoader.getSystemResources(fullName);
 else
 configs = 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());
 }
 // 这里将下一个实现类的名字赋值给了LazyIterator的属性nextName
 nextName = pending.next();
 return true;
 }

7.接下来执行的是 NinjaService item = (NinjaService)var2.next()的next(方法),然后继续debug进去,这里我省略了一些方法的调用,只展示出有用的方法这个是ServiceLoader的内部类LazyIterator的nextService()方法.

private S nextService() {
 if (!hasNextService())
 throw new NoSuchElementException();
 String cn = nextName;
 nextName = null;
 Class<?> c = null;
 try {
 // 这里nextName在上面已经赋值过了 所以反射创建实例
 c = Class.forName(cn, false, loader);
 } catch (ClassNotFoundException x) {
 fail(service,
 "Provider " + cn + " not found");
 }
 // 类型判断
 if (!service.isAssignableFrom(c)) {
 fail(service,
 "Provider " + cn + " not a subtype");
 }
 try {
 // 强转类型
 S p = service.cast(c.newInstance());
 // 将类添加到ServiceLoader的providers属性中 然后返回
 providers.put(cn, p);
 return p;
 } catch (Throwable x) {
 fail(service,
 "Provider " + cn + " could not be instantiated",
 x);
 }
 throw new Error(); // This cannot happen
 }

8.到这里子类的实现类返回,分析就结束了.

总结:

1.了解了什么是SPI;

2.SPI和API的简单区别和联系;

3.学习了怎么使用SPI来扩展服务;

4.分析了ServiceLoader的源码加载过程,这里扯一句,简单的就是META-INF/services定义好要实现的接口(文件名)和实现类(文件内容), ServiceLoader加载的时候没有实例化实现类,而是在Iterator遍历的时候去用反射创建了实例.
 



Tags:SPI   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
因为最近项目中使用分库分表以及数据加密使用到了 ShardingSphere,所以决定这段时间看看源码实现。问我为什么要读源码?不看源码怎么提高逼格嘞,就是这么朴实无华~...【详细内容】
2021-06-01  Tags: SPI  点击:(148)  评论:(0)  加入收藏
一 网关的位置和作用网关作为流量的入口,常用功能包括路由转发、权限校验、限流控制等。Spring Cloud gateway作为 Spring Cloud 官方推出的第二代网关框架,取代了 Zuul网关。...【详细内容】
2020-10-20  Tags: SPI  点击:(157)  评论:(0)  加入收藏
1、 SPI简介SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实...【详细内容】
2020-10-19  Tags: SPI  点击:(80)  评论:(0)  加入收藏
ReconSpider是一款功能强大的高级公开资源情报(OSINT)框架,可以帮助广大研究人员扫描目标IP地址、电子邮件、网站和组织信息,并从不同消息源收集各种情报信息。ReconSpider适用...【详细内容】
2020-10-19  Tags: SPI  点击:(87)  评论:(0)  加入收藏
一个基于Redis实现的接口限流方案,先说要实现的功能 可以限制指定的接口,在一定时间内,只能被请求N次,超过次数就返回异常信息 可以通过配置文件,或者管理后台,动态的修改限流配置...【详细内容】
2020-08-10  Tags: SPI  点击:(52)  评论:(0)  加入收藏
作者:crossoverJie前言不知大家现在有没有去公司复工,我已经在家办公将近 3 周了,同时也在家呆了一个多月;还好工作并没有受到任何影响,我个人一直觉得远程工作和 IT 行业是非常...【详细内容】
2020-02-11  Tags: SPI  点击:(45)  评论:(0)  加入收藏
互联网信息爆发式增长,如何有效的获取并利用这些信息是搜索引擎工作中的首要环节。数据抓取系统作为整个搜索系统中的上游,主要负责互联网信息的搜集、保存、更新环节,它像蜘蛛...【详细内容】
2019-12-11  Tags: SPI  点击:(131)  评论:(0)  加入收藏
Baiduspider(百度蜘蛛)抓取频次原则及调整方法Baiduspider根据网站设置的协议对站点页面进行抓取,但是不可能做到对所有站点一视同仁,会综合考虑站点实际情况确定一个抓取配额,每...【详细内容】
2019-10-28  Tags: SPI  点击:(181)  评论:(0)  加入收藏
PySpider 是一个非常方便并且功能强大的爬虫框架,支持多线程爬取、JS动态解析,提供了可操作界面、出错重试、定时爬取等等的功能,使用非常人性化。能够在需要编写大量爬虫的情...【详细内容】
2019-08-26  Tags: SPI  点击:(352)  评论:(0)  加入收藏
(一)ping命令:可用于判断网络是否连通通过ping+ 远程Ip地址 来判断本网或者本机与外部的连接是否正常。例如:ping www.baidu.com 通过用时和时候丢包来判断本机是否能够联网。...【详细内容】
2019-07-16  Tags: SPI  点击:(856)  评论:(0)  加入收藏
▌简易百科推荐
面向对象的特征之一封装 面向对象的特征之二继承 方法重写(override/overWrite) 方法的重载(overload)和重写(override)的区别: 面向对象特征之三:多态 Instanceof关键字...【详细内容】
2021-12-28  顶顶架构师    Tags:面向对象   点击:(2)  评论:(0)  加入收藏
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(12)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(13)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(11)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(11)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(17)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(19)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(22)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条