当前位置: 首页 > news >正文

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的简写&#xff1b; JDK内置的服务提供加载机制&#xff0c;可以为服务接口加载实现类&#xff0c;解耦是其核心思想&#xff0c;也是很多框架和组件的常用手段&#xff1b; 2、入门案例 2.1 定义接口 …...

DSL:数字用户线路(Digital Subscriber Line)

一、基础释义 DSL&#xff08;数字用户线路&#xff0c;Digital Subscriber Line&#xff09;是一种用于传输数字数据的通信技术&#xff0c;允许数据在传统的电话线路&#xff08;铜线&#xff09;上进行高速传输。DSL技术通过将高频信号叠加在低频的语音信号上&#xff0c;使…...

Java集合ArrayList详解

ArrayList 类是一个可以动态修改的数组&#xff0c;与普通数组的区别就是它是没有固定大小的限制&#xff0c;我们可以添加或删除元素。 ArrayList 继承了 AbstractList &#xff0c;并实现了 List 接口。 Java 数组 与 ArrayList 在Java中&#xff0c;我们需要先声明数组的大…...

React Dva项目 Model中编写与调用异步函数

上文 React Dva项目中模仿网络请求数据方法 中&#xff0c;我们用项目方法模拟了后端请求的数据 那么 今天我们就在models中尝试去使用一下这种异步获取数据的方法 之前 我们在文章 React Dva项目创建Model,并演示数据管理与函数调用 中已经接触过Model了 也可以理解为 它就是 …...

小程序自定义tabBar+Vant weapp

1.构建npm&#xff0c;安装Vant weapp&#xff1a; 1&#xff09;根目录下 &#xff0c;初始化生成依赖文件package.json npm init -y 2&#xff09;安装vant # 通过 npm 安装 npm i vant/weapp -S --production 3&#xff09;修改 package.json 文件 开发者工具创建的项…...

Dubbo+Zookeeper使用

说明&#xff1a;Apache Dubbo 是一款 RPC 服务开发框架&#xff0c;用于解决微服务架构下的服务治理与通信问题&#xff0c;官方提供了 Java、Golang 等多语言 SDK 实现。 本文介绍Dubbo的简单使用及一些Dubbo功能特性&#xff0c;注册中心使用的是ZooKeeper&#xff0c;可在…...

短视频平台视频怎么去掉水印?

短视频怎么去水印&#xff0c;困扰很多人&#xff0c;例如&#xff0c;有些logo水印&#xff0c;动态水印等等&#xff0c;分享操作经验&#xff1a; 抖音作为中国最受欢迎的社交娱乐应用程序之一&#xff0c;已成为许多人日常生活中不可或缺的一部分。在使用抖音过程中&#x…...

Stable Diffusion - Style Editor 和 Easy Prompt Selector 提示词插件配置

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132122450 Style Editor 插件&#xff1a; cd extensions git clone https://ghproxy.com/https://github.com/chrisgoringe/Styles-Editor报错&…...

Stable Diffusion - SDXL 模型测试 (DreamShaper 和 GuoFeng v4) 与全身图像参数配置

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/132085757 图像来源于 GuoFeng v4 XL 模型&#xff0c;艺术风格是赛博朋克、漫画、奇幻。 全身图像是指拍摄对象的整个身体都在画面中的照片&…...

中介者模式(Mediator)

中介者模式是一种行为设计模式&#xff0c;可以减少对象之间混乱无序的依赖关系。该模式会限制对象之间的直接交互&#xff0c;迫使它们通过一个封装了对象间交互行为的中介者对象来进行合作&#xff0c;从而使对象间耦合松散&#xff0c;并可独立地改变它们之间的交互。中介者…...

SpringBoot使用@Autowired将实现类注入到List或者Map集合中

前言 最近看到RuoYi-Vue-Plus翻译功能 Translation的翻译模块配置类TranslationConfig&#xff0c;其中有一个注入TranslationInterface翻译接口实现类的写法让我感到很新颖&#xff0c;但这种写法在Spring 3.0版本以后就已经支持注入List和Map&#xff0c;平时都没有注意到这…...

【linux目录的权限和粘滞位】

目录&#xff1a; 目录的权限粘滞位总结 目录的权限 可执行权限: 如果目录没有可执行权限, 则无法cd到目录中. 可读权限: 如果目录没有可读权限, 则无法用ls等命令查看目录中的文件内容. 可写权限: 如果目录没有可写权限, 则无法在目录中创建文件, 也无法在目录中删除文件. 粘…...

TP DP PP 并行训练方法介绍

这里写目录标题 张量并行TP流水线并行 PPnaive模型并行GPipePipeDream 数据并行DPFSDP 张量并行TP 挖坑 流水线并行 PP 经典的流水线并行范式有Google推出的Gpipe&#xff0c;和微软推出的PipeDream。两者的推出时间都在2019年左右&#xff0c;大体设计框架一致。主要差别为…...

P005 – Python操作符、操作数和表达式

在Python中&#xff0c;操作符用于对值或变量进行操作。操作数是操作符作用的值或变量。表达式是由操作符、操作数和其他表达式组合而成的&#xff0c;可以求得一个值。 在本文中&#xff0c;我们将探讨Python中的不同类型的操作符&#xff0c;学习如何与操作数一起使用它们来…...

SQL92 SQL99 语法 Oracle 、SQL Server 、MySQL 多表连接、Natural 、USING

SQL92 VS SQL 99 语法 92语法 内连接 from table1&#xff0c; table2 where table1.col table2.col 外连接 放在 从表 左连接&#xff1a; from table1&#xff0c; table2 where table1.col table2.col() 右连接&#xff1a; from table1&#xff0c; table2 where table…...

物联网平台使用笔记

阿里云的IOT平台限制了50个设备。排除 移动云的限制较少&#xff0c;这里试用下。 创建完产品&#xff0c;接入设备后。使用MQTT客户端测试 其中client id 为设备id&#xff0c; username 为产品id&#xff0c; 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…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)

目录 1.TCP的连接管理机制&#xff08;1&#xff09;三次握手①握手过程②对握手过程的理解 &#xff08;2&#xff09;四次挥手&#xff08;3&#xff09;握手和挥手的触发&#xff08;4&#xff09;状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

unix/linux,sudo,其发展历程详细时间线、由来、历史背景

sudo 的诞生和演化,本身就是一部 Unix/Linux 系统管理哲学变迁的微缩史。来,让我们拨开时间的迷雾,一同探寻 sudo 那波澜壮阔(也颇为实用主义)的发展历程。 历史背景:su的时代与困境 ( 20 世纪 70 年代 - 80 年代初) 在 sudo 出现之前,Unix 系统管理员和需要特权操作的…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容

目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法&#xff0c;当前调用一个医疗行业的AI识别算法后返回…...

html-<abbr> 缩写或首字母缩略词

定义与作用 <abbr> 标签用于表示缩写或首字母缩略词&#xff0c;它可以帮助用户更好地理解缩写的含义&#xff0c;尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时&#xff0c;会显示一个提示框。 示例&#x…...

智能AI电话机器人系统的识别能力现状与发展水平

一、引言 随着人工智能技术的飞速发展&#xff0c;AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术&#xff0c;在客户服务、营销推广、信息查询等领域发挥着越来越重要…...