JDK中的SPI机制
前言
最近学习类加载的过程中,了解到JDK提供给我们的一个可扩展的接口:java.util.ServiceLoader
,
之前自己不了解这个机制,甚是惭愧...
什么是SPI
SPI全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制,比如有个接口,想运行时动态的给它添加实现,你只需要添加一个实现。我们经常遇到的就是java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,mysql和postgresql都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。
首先放个图:我们在“调用方”和“实现方”之间需要引入“接口”,可以思考一下什么情况应该把接口放入调用方,什么时候可以把接口归为实现方。
先来看看接口属于实现方的情况,这个很容易理解,实现方提供了接口和实现,我们可以引用接口来达到调用某实现类的功能,这就是我们经常说的api,它具有以下特征:
1.是对实现的说明(我可以给你提供什么)
2.组织上位于实现方所在的包中
3.实现和接口在一个包中
当接口属于调用方时,我们就将其称为spi,全称为:service provider interface,spi的规则如下:
1.是对实现的约束(要提供这个功能,实现者需要做那些事情)
2.组织上位于调用方所在的包中
3.实现位于独立的包中(也可认为在提供方中)
简而言之
API会告诉您特定的类/方法为您执行什么操作,而SPI则告诉您必须执行哪些操作才能符合要求。通常,API和SPI是分开的。例如,在JDBC中,Driver类是SPI的一部分:如果只想使用JDBC,则不需要直接使用它,但是实现JDBC驱动程序的每个人都必须实现该类。但是,有时它们会重叠。Connection接口既是SPI,又是API:您在使用JDBC驱动程序时通常会使用它,并且需要由JDBC驱动程序的开发人员来实现。
JDK SPI使用说明及示例
要使用SPI比较简单,只需要按照以下几个步骤操作即可:
1.在jar包的META-INF/services目录下创建一个以"接口全限定名"为命名的文件,内容为实现类的全限定名
2.接口实现类所在的jar包在classpath下
3.主程序通过java.util.ServiceLoader动态状态实现模块,它通过扫描META-INF/services目录下的配置文件找到实现类的全限定名,把类加载到JVM
4.SPI的实现类必须带一个无参构造方法
举例1
下例是使用maven引入了mysql的依赖后执行的,MySQL驱动内的截图:
java.sql.Driver文件全部内容:
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
public class SpiTest {
public static void main(String[] args) {
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = loader.iterator();
while(iterator.hasNext()) {
Driver driver = iterator.next();
System.out.println("driver is " + driver.getClass() + ", classLoader is " + driver.getClass().getClassLoader());
}
System.out.println("当前上下文类加载器是:" + Thread.currentThread().getContextClassLoader());
System.out.println("ServiceLoader的类加载器是:" + ServiceLoader.class.getClassLoader());
}
}
运行结果:
driver is class com.mysql.jdbc.Driver, classLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
driver is class com.mysql.fabric.jdbc.FabricMySQLDriver, classLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
当前上下文类加载器是:sun.misc.Launcher$AppClassLoader@18b4aac2
ServiceLoader的类加载器是:null
SPI相关的类加载的逻辑
因为ServiceLoader位于java.util.ServiceLoader
,所以这个类是会被启动类加载器所加载,然后我们分析一下ServiceLoader.load(Driver.class)
的源码。
根据类加载的原理:如果一个类由类加载器A加载,那么这个类的依赖类也会被类加载器A加载(前提是这个依赖类尚未被加载过)。
当执行ServiceLoader.load(Driver.class),如果不使用线程上下文类加载器来打破双亲委托模型,那么该方法的关联类也会被启动类加载器加载。
333 public static <S> ServiceLoader<S> load(Class<S> service) {
334 ClassLoader cl = Thread.currentThread().getContextClassLoader();
335 return ServiceLoader.load(service, cl);
336 }
所以我们看到334行获取了线程上下文类加载器,然后调用另一个重载的load方法去加载Driver(即所谓的Service)。
继续跟踪ServiceLoader.load(service, cl)
的代码,会发现它依次执行了如下动作:
1.初始化了一个:ServiceLoader对象,new ServiceLoader<>(service, loader)
2.执行reload()
3.new LazyIterator(service, loader)
这里所有的loader都是线程上下文类加载器,它默认为系统类加载器。
这时,SpiTest中的ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
就执行完了,
跟着执行SpiTest中的Iterator<Driver> iterator = loader.iterator();
这里当执行iterator.hasNext()
的时候,就会进入到刚才初始化的LazyIterator类中,执行其中的下列方法,
所以这也是为什么"在jar包的META-INF/services目录下创建一个以"接口全限定名"为命名的文件,内容为实现类的全限定名"
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//这里的PREFIX是一个常量:META-INF/services/
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());
}
nextName = pending.next();
return true;
}
仔细阅读内部类LazyIterator类的源码,就可以知道:
1.JDK是怎么读取META-INF/services目录下的内容
2.JDK是怎么加载这些SPI的类的
JDK SPI的不足
JDK SPI的使用很简单。也做到了基本的加载扩展点的功能。但JDK SPI有以下的不足:
1.需要遍历所有的实现,并实例化,然后我们在循环中才能找到我们需要的实现。
2.配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。
3.扩展如果依赖其他的扩展,做不到自动注入和装配
4.不提供类似于Spring的IOC和AOP功能
5.扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的JDK SPI不支持
JDK中的SPI机制的更多相关文章
- Dubbo SPI机制之一JDK中的SPI
首先简单阐述下什么是SPI:SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制.目前有不少框架用它来做服务的扩展发现,简单来说,就是一种动态 ...
- 【Java】深入理解Java中的spi机制
深入理解Java中的spi机制 SPI全名为Service Provider Interface是JDK内置的一种服务提供发现机制,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用 ...
- 结合实战和源码来聊聊Java中的SPI机制?
写在前面 SPI机制能够非常方便的为某个接口动态指定其实现类,在某种程度上,这也是某些框架具有高度可扩展性的基础.今天,我们就从源码级别深入探讨下Java中的SPI机制. 注:文章已收录到:https ...
- java中的SPI机制
1 SPI机制简介 SPI的全名为Service Provider Interface.大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的.在java.util.ServiceLoader的文档里 ...
- Java 中的 SPI 机制是什么鬼?高级 Java 必须掌握!
作者:sigangjun blog.csdn.net/sigangjun/article/details/79071850 SPI的全名为:Service Provider Interface,大多数 ...
- 一文搞懂Java/Spring/Dubbo框架中的SPI机制
几天前和一位前辈聊起了Spring技术,大佬突然说了SPI,作为一个熟练使用Spring的民工,心中一紧,咱也不敢说不懂,而是在聊完之后赶紧打开了浏览器,开始的学习之路,所以也就有了这篇文章.废话不多 ...
- Skywalking-12:Skywalking SPI机制
SPI机制 基本概述 SPI 全称 Service Provider Interface ,是一种服务发现机制.通过提供接口.预定义的加载器( Loader )以及约定俗称的配置(一般在 META-I ...
- 面试常问的dubbo的spi机制到底是什么?
前言 dubbo是一款微服务开发框架,它提供了 RPC通信 与 微服务治理 两大关键能力.作为spring cloud alibaba体系中重要的一部分,随着spring cloud alibaba在 ...
- Java中的SPI原理浅谈
在面向对象的程序设计中,模块之间交互采用接口编程,通常情况下调用方不需要知道被调用方的内部实现细节,因为一旦涉及到了具体实现,如果需要换一种实现就需要修改代码,这违反了程序设计的"开闭原则& ...
随机推荐
- 什么是云效持续集成?如何关联Jenkins进行持续集成?
什么是云效持续集成?如何关联Jenkins进行持续集成?云效流水线 Flow是一款企业级.自动化的研发交付流水线, 提供灵活易用的持续集成.持续验证. 持续发布功能,帮助企业高质量.高效率的交付业务. ...
- Linux触摸驱动分析
测试平台 宿主机平台:Ubuntu 12.04.4 LTS 目标机:Easy-ARM IMX283 目标机内核:Linux 2.6.35.3 触摸屏基础知识 一.结构 上图是电阻触摸屏的一个侧面剖视图 ...
- Axis <=1.4 RCE 复现
1.环境搭建 在idea 上新建项目,然后用tomcat运行即可 2.漏洞复现 2.1 freemarker.template.utility.Execute 如果项目里面没有freemarker 就 ...
- 利用nginx 来实现内网yum源(反向代理)
简介 在项目部署时,尤其是在政府企业,对于外网简直是奢望,但是对于运维来说,没有外网的话只能自建yum源.我今天来说的是一种简单的自建yum源方法,前提是必须有一台内外网都有的机器,我们一般称为前置机 ...
- Maven专题1——坐标与依赖
1. 坐标 坐标用来唯一定位一个Maven构件: GAV(必需):groupId, artifactId, version packaging(可选): 可取值如:jar(缺省), war, pom, ...
- UVA 1572 Self-Assembly(拓扑排序)
1 // 把一个图的所有结点排序,使得每一条有向边(u,v)对应的u都排在v的前面. 2 // 在图论中,这个问题称为拓扑排序.(toposort) 3 // 不难发现:如果图中存在有向环,则不存在拓 ...
- git介绍-常用操作(一)
Table of Contents 1 系列文章 2 git说明 3 git常用命令 3.1 基本操作 3.2 远程操作 4 查看git的配置 4.1 查看已配置项 4.2 其他配置 ...
- PHP没有定时器?
确实,PHP没有类似于JS中的setInterval或者setTimeout这样的原生定时器相关的函数.但是我们可以通过其他方式来实现,比如使用declare. 先来看看是如何实现的,然后我们再好好学 ...
- 在PHP中如何为匿名函数指定this?
在之前的文章中,我们已经学习过匿名函数的使用,没有看过的小伙伴可以进入传送门先去了解下闭包匿名函数的用法,传送:还不知道PHP有闭包?那你真OUT了. 关于闭包匿名函数,在JS中有个很典型的问题就是要 ...
- Jmeter系列(19)- 常用配置文件
JMeter.properties :跟Jmeter配置相关的配置信息都在这边,比如:Jmeter GUI页面的语言.日志级别设置等 User.properties:用户自定义相关的所有变量,会复写J ...