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原理浅谈
在面向对象的程序设计中,模块之间交互采用接口编程,通常情况下调用方不需要知道被调用方的内部实现细节,因为一旦涉及到了具体实现,如果需要换一种实现就需要修改代码,这违反了程序设计的"开闭原则& ...
随机推荐
- Redis详解(一)——
Redis详解1 https://www.cnblogs.com/MoYu-zc/p/14985250.html https://www.cnblogs.com/xiaoxiaotank/p/1498 ...
- Django——cookie保持登录
普通登录时,无法保持登录的状态,每一次请求时都需要重新登录. 而在登录时,生成cookie并保存在浏览器中,这样每次登录就会携带登录信息,就可以保持登录状态了. 操作cookie语法: # (1) 设 ...
- Selenium系列(十八) - Web UI 自动化基础实战(5)
如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...
- 【曹工杂谈】Maven IOC容器的下半场:Google Guice
Maven容器的下半场:Guice 前言 在前面的文章里,Maven底层容器Plexus Container的前世今生,一代芳华终落幕,我们提到,在Plexus Container退任后,取而代之的底 ...
- MySql分表、分库、分片和分区的区别
一.前言 数据库的数据量达到一定程度之后,为避免带来系统性能上的瓶颈.需要进行数据的处理,采用的手段是分区.分片.分库.分表. 二.分片(类似分库) 分片是把数据库横向扩展(Scale Out)到多个 ...
- Dockerfile 自动制作 Docker 镜像(三)—— 镜像的分层与 Dockerfile 的优化
Dockerfile 自动制作 Docker 镜像(三)-- 镜像的分层与 Dockerfile 的优化 前言 a. 本文主要为 Docker的视频教程 笔记. b. 环境为 CentOS 7.0 云 ...
- Docker容器基本命令注意点
Docker 容器基本命令注意点 前言: a. 本文主要为 Docker的视频教程 笔记. b. 本机环境为 Windows 10 专业版,使用的命令行为 PowerShell. 1. docker ...
- go语言游戏服务端开发(三)——服务机制
五邑隐侠,本名关健昌,12年游戏生涯. 本教程以Go语言为例. P2P网络为服务进程间.服务进程与客户端间通信提供了便利,在这个基础上可以搭建服务. 在服务层,通信包可以通过定义协议号来确定该包怎 ...
- TS基础笔记
TS优势 更好的错误的提示,开发中及时发现问题:编辑器语法提示更完善:类型声明可以看出数据结构的语义,可读性更好; TS环境搭建 1.安装node;2.npm install typescript@3 ...
- PHP中的强制类型转换
学过静态语言开发的朋友对类型转换不会陌生,比如Java.C#.C++等.静态语言的好处就是变量强制必须指定类型,这也是编译的要求,所以大部分编译型的语言都会有强制变量类型的要求.而PHP据说也会在PH ...