JDK中「SPI」原理分析
基于【JDK1.8】
一、SPI简介
1、概念
SPI即service-provider-interface的简写;
JDK内置的服务提供加载机制,可以为服务接口加载实现类,解耦是其核心思想,也是很多框架和组件的常用手段;
2、入门案例

2.1 定义接口
就是普通的接口,在SPI的机制中称为【service】,即服务;
public interface Animal {String animalName () ;
}
2.2 两个实现类
提供两个模拟用来测试,就是普通的接口实现类,在SPI的机制中称为【service-provider】即服务提供方;
CatAnimal实现类;
public class CatAnimal implements Animal {@Overridepublic String animalName() {System.out.println("Cat-Animal:布偶猫");return "Ragdoll";}
}
DogAnimal实现类;
public class DogAnimal implements Animal {@Overridepublic String animalName() {System.out.println("Dog-Animal:哈士奇");return "husky";}
}
2.3 配置文件
文件目录:在代码工程中创建META-INF.services文件夹;
文件命名:butte.program.basics.spi.inf.Animal,即全限定接口名称;
文件内容:添加相应实现类的全限定命名;
butte.program.basics.spi.impl.CatAnimal
butte.program.basics.spi.impl.DogAnimal
2.4 测试代码
通过ServiceLoader加载配置文件中指定的服务实现类,然后遍历并调用Animal接口方法,从而执行不同服务提供方的具体逻辑;
public class SpiAnaly {public static void main(String[] args) {ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);Iterator<Animal> animalIterator = serviceLoader.iterator();while(animalIterator.hasNext()) {Animal animal = animalIterator.next();System.out.println("animal-name:" + animal.animalName());}}
}
结果输出
Cat-Animal:布偶猫 \n animal-name:ragdoll
Dog-Animal:哈士奇 \n animal-name:husky
二、原理分析
1、ServiceLoader结构
很显然,分析SPI机制的原理,从ServiceLoader源码中load方法切入即可,但是需要先从核心类的结构开始分析;
public final class ServiceLoader<S> implements Iterable<S> {// 配置文件目录private static final String PREFIX = "META-INF/services/";// 表示正在加载的服务的类或接口private final Class<S> service;// 类加载器用来定位,加载,实例化服务提供方private final ClassLoader loader;// 创建ServiceLoader时采用的访问控制上下文private final AccessControlContext acc;// 按实例化的顺序缓存服务提供方private LinkedHashMap<String,S> providers = new LinkedHashMap<>();// 惰性查找迭代器private LazyIterator lookupIterator;/*** service:表示服务的接口或抽象类* loader: 类加载器*/public static <S> ServiceLoader<S> load(Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);}/*** ServiceLoader构造方法*/private ServiceLoader(Class<S> svc, ClassLoader cl) {loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;reload();}public void reload() {providers.clear();// 实例化迭代器lookupIterator = new LazyIterator(service, loader);}public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader) {return new ServiceLoader<>(service, loader);}private class LazyIterator implements Iterator<S> {// 服务接口Class<S> service;// 类加载器ClassLoader loader;// 实现类URLEnumeration<URL> configs = null;// 实现类全名Iterator<String> pending = null;// 下个实现类全名String nextName = null;}
}
断点截图:

2、iterator迭代方法
在ServiceLoader类的迭代器方法中,实际使用的是LazyIterator内部类的方法;
public Iterator<S> iterator() {return new Iterator<S>() {Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();public boolean hasNext() {if (knownProviders.hasNext())return true;return lookupIterator.hasNext();}public S next() {if (knownProviders.hasNext())return knownProviders.next().getValue();return lookupIterator.next();}public void remove() {throw new UnsupportedOperationException();}};
}
3、hasNextService方法
从上面迭代方法的源码中可知,最终执行的是LazyIterator#hasNextService判断方法,该方法通过解析最终会得到实现类的全限定名称;
private class LazyIterator implements Iterator<S> {private boolean hasNextService() {// 1、拼接名称String fullName = PREFIX + service.getName();// 2、加载资源文件configs = loader.getResources(fullName);// 3、解析文件内容pending = parse(service, configs.nextElement());nextName = pending.next();return true;}
}
断点截图:

4、nextService方法
迭代器的next方法最终执行的是LazyIterator#nextService获取方法,会基于上面hasNextService方法获取的实现类全限定名称,获取其Class对象,进而得到实例化对象,缓存并返回;
private class LazyIterator implements Iterator<S> {private S nextService() {// 1、通过全限定命名获取Class对象String cn = nextName;Class<?> c = Class.forName(cn, false, loader);// 2、实例化对象S p = service.cast(c.newInstance());// 3、放入缓存并返回该对象providers.put(cn, p);return p;}
}
断点截图:

三、SPI实践
1、Driver驱动接口
在JDK中提供了数据库驱动接口java.sql.Driver,无论是MySQL驱动包还是Druid连接池,都提供了该接口的实现类,通过SPI机制可以加载到这些驱动实现类;
public class DriverManager {private static void loadInitialDrivers() {AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(java.sql.Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();}});}
}
断点截图:

2、Slf4j日志接口
SLF4J是门面模式的日志组件,提供了标准的日志服务SLF4JServiceProvider接口,在LogFactory日志工厂类中,负责加载具体的日志实现类,比如常用的Log4j或Logback日志组件;
public final class LoggerFactory {static List<SLF4JServiceProvider> findServiceProviders() {// 服务加载ClassLoader classLoaderOfLoggerFactory = org.slf4j.LoggerFactory.class.getClassLoader();// 重点看该方法:【getServiceLoader()】ServiceLoader<SLF4JServiceProvider> serviceLoader = getServiceLoader(classLoaderOfLoggerFactory);// 迭代方法List<SLF4JServiceProvider> providerList = new ArrayList();Iterator<SLF4JServiceProvider> iterator = serviceLoader.iterator();while(iterator.hasNext()) {safelyInstantiate(providerList, iterator);}return providerList;}
}
断点截图:

四、参考源码
文档仓库:
https://gitee.com/cicadasmile/butte-java-note应用仓库:
https://gitee.com/cicadasmile/butte-flyer-parent
相关文章:
JDK中「SPI」原理分析
基于【JDK1.8】 一、SPI简介 1、概念 SPI即service-provider-interface的简写; JDK内置的服务提供加载机制,可以为服务接口加载实现类,解耦是其核心思想,也是很多框架和组件的常用手段; 2、入门案例 2.1 定义接口 …...
DSL:数字用户线路(Digital Subscriber Line)
一、基础释义 DSL(数字用户线路,Digital Subscriber Line)是一种用于传输数字数据的通信技术,允许数据在传统的电话线路(铜线)上进行高速传输。DSL技术通过将高频信号叠加在低频的语音信号上,使…...
Java集合ArrayList详解
ArrayList 类是一个可以动态修改的数组,与普通数组的区别就是它是没有固定大小的限制,我们可以添加或删除元素。 ArrayList 继承了 AbstractList ,并实现了 List 接口。 Java 数组 与 ArrayList 在Java中,我们需要先声明数组的大…...
React Dva项目 Model中编写与调用异步函数
上文 React Dva项目中模仿网络请求数据方法 中,我们用项目方法模拟了后端请求的数据 那么 今天我们就在models中尝试去使用一下这种异步获取数据的方法 之前 我们在文章 React Dva项目创建Model,并演示数据管理与函数调用 中已经接触过Model了 也可以理解为 它就是 …...
小程序自定义tabBar+Vant weapp
1.构建npm,安装Vant weapp: 1)根目录下 ,初始化生成依赖文件package.json npm init -y 2)安装vant # 通过 npm 安装 npm i vant/weapp -S --production 3)修改 package.json 文件 开发者工具创建的项…...
Dubbo+Zookeeper使用
说明:Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。 本文介绍Dubbo的简单使用及一些Dubbo功能特性,注册中心使用的是ZooKeeper,可在…...
短视频平台视频怎么去掉水印?
短视频怎么去水印,困扰很多人,例如,有些logo水印,动态水印等等,分享操作经验: 抖音作为中国最受欢迎的社交娱乐应用程序之一,已成为许多人日常生活中不可或缺的一部分。在使用抖音过程中&#x…...
Stable Diffusion - Style Editor 和 Easy Prompt Selector 提示词插件配置
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/132122450 Style Editor 插件: cd extensions git clone https://ghproxy.com/https://github.com/chrisgoringe/Styles-Editor报错&…...
Stable Diffusion - SDXL 模型测试 (DreamShaper 和 GuoFeng v4) 与全身图像参数配置
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/132085757 图像来源于 GuoFeng v4 XL 模型,艺术风格是赛博朋克、漫画、奇幻。 全身图像是指拍摄对象的整个身体都在画面中的照片&…...
中介者模式(Mediator)
中介者模式是一种行为设计模式,可以减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互,迫使它们通过一个封装了对象间交互行为的中介者对象来进行合作,从而使对象间耦合松散,并可独立地改变它们之间的交互。中介者…...
SpringBoot使用@Autowired将实现类注入到List或者Map集合中
前言 最近看到RuoYi-Vue-Plus翻译功能 Translation的翻译模块配置类TranslationConfig,其中有一个注入TranslationInterface翻译接口实现类的写法让我感到很新颖,但这种写法在Spring 3.0版本以后就已经支持注入List和Map,平时都没有注意到这…...
【linux目录的权限和粘滞位】
目录: 目录的权限粘滞位总结 目录的权限 可执行权限: 如果目录没有可执行权限, 则无法cd到目录中. 可读权限: 如果目录没有可读权限, 则无法用ls等命令查看目录中的文件内容. 可写权限: 如果目录没有可写权限, 则无法在目录中创建文件, 也无法在目录中删除文件. 粘…...
TP DP PP 并行训练方法介绍
这里写目录标题 张量并行TP流水线并行 PPnaive模型并行GPipePipeDream 数据并行DPFSDP 张量并行TP 挖坑 流水线并行 PP 经典的流水线并行范式有Google推出的Gpipe,和微软推出的PipeDream。两者的推出时间都在2019年左右,大体设计框架一致。主要差别为…...
P005 – Python操作符、操作数和表达式
在Python中,操作符用于对值或变量进行操作。操作数是操作符作用的值或变量。表达式是由操作符、操作数和其他表达式组合而成的,可以求得一个值。 在本文中,我们将探讨Python中的不同类型的操作符,学习如何与操作数一起使用它们来…...
SQL92 SQL99 语法 Oracle 、SQL Server 、MySQL 多表连接、Natural 、USING
SQL92 VS SQL 99 语法 92语法 内连接 from table1, table2 where table1.col table2.col 外连接 放在 从表 左连接: from table1, table2 where table1.col table2.col() 右连接: from table1, table2 where table…...
物联网平台使用笔记
阿里云的IOT平台限制了50个设备。排除 移动云的限制较少,这里试用下。 创建完产品,接入设备后。使用MQTT客户端测试 其中client id 为设备id, username 为产品id, password 可以使用设备调试那里生成的。或使用官方token.exe 生成…...
Python-flask项目入门
一、flask对于简单搭建一个基于python语言-的web项目非常简单 二、项目目录 示例代码 git路径 三、代码介绍 1、安装pip依赖 通过pip插入数据驱动依赖pip install flask-sqlalchemy 和 pip install pymysql 2.配置数据源 config.py DIALECT mysql DRIVER pymysql USERN…...
基于数据库 Sqlite3 的 root 管理系统
1.服务器 1.1服务器函数入口 #include "server.h"int main(int argc, char const *argv[]) {char buf[128] {0};char buf_ID[256] {0};// 接收报错信息判断sqlite3 *db;// 创建员工信息的表格,存在则打开db Sqlite_Create();if (db NULL){printf("sqlite_…...
Hadoop 之 Hive 4.0.0-alpha-2 搭建(八)
Hadoop 之 Hive 搭建与使用 一.Hive 简介二.Hive 搭建1.下载2.安装1.解压并配置 HIVE2.修改 hive-site.xml3.修改 hadoop 的 core-site.xml4.启动 三.Hive 测试1.基础测试2.建库建表3.Java 连接测试1.Pom依赖2.Yarm 配置文件3.启动类4.配置类5.测试类 一.Hive 简介 Hive 是基于…...
vue3常用API之学习笔记
目录 一、setup函数 vue2与vue3变量区别 二、生命周期 三、reactive方法 四、ref方法 1、简介 2、使用 3、ref与reactive 4、获取标签元素或组件 五、toRef 1、简介 2、ref与toRef的区别 六、toRefs 七、shallowReactive 浅reactive 1、简介 2、shallowreactiv…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...
相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...
6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础
第三周 Day 3 🎯 今日目标 理解类(class)和对象(object)的关系学会定义类的属性、方法和构造函数(init)掌握对象的创建与使用初识封装、继承和多态的基本概念(预告) &a…...
图解JavaScript原型:原型链及其分析 | JavaScript图解
忽略该图的细节(如内存地址值没有用二进制) 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么:保存在堆中一块区域,同时在栈中有一块区域保存其在堆中的地址(也就是我们通常说的该变量指向谁&…...
渗透实战PortSwigger Labs指南:自定义标签XSS和SVG XSS利用
阻止除自定义标签之外的所有标签 先输入一些标签测试,说是全部标签都被禁了 除了自定义的 自定义<my-tag onmouseoveralert(xss)> <my-tag idx onfocusalert(document.cookie) tabindex1> onfocus 当元素获得焦点时(如通过点击或键盘导航&…...
