SPI 动态服务发现机制
SPI(Service Provier Interface)是一种服务发现机制,通过ClassPath下的META—INF/services文件查找文件,自动加载文件中定义的类,再调用forName加载;
spi可以很灵活的让接口和实现分离, 让API提供者只提供接口, 第三方来实现。
优点:
- 使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。
缺点:
- 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
- 多个并发多线程使用ServiceLoader类的实例是不安全的。
Service 机制分析
第一步:ServiceLoader.load(Class clz);
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
我们看一下 ServiceLoader.load(Driver.class);是如何工作的,注意看第二行代码切换了上下文的 classLoader,一般为 AppClassLoader。
public static <S> ServiceLoader<S> load(Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);}
ServiceLoader.load(service, cl);接着进入 load 方法
public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){return new ServiceLoader<>(service, loader);}
看一下 ServiceLoader 的构造方法,这里可能会进行 ClassLoader 的切换,这不太重要,我们看一下 reload();
private ServiceLoader(Class<S> svc, ClassLoader cl) {service = Objects.requireNonNull(svc, "Service interface cannot be null");loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;reload();}
reload();方法构造了一个LazyIterator,继续看
public void reload() {providers.clear();lookupIterator = new LazyIterator(service, loader);}
第二步:利用第一步构造出来的LazyIterator,循环Class.forName(),加载接口实现类
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}
loadedDrivers.iterator();迭代器在迭代时是会用到第一步创建的lookupIterator = new LazyIterator(service, loader);
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();}};}
当我们进行driversIterator.next();时,实际上执行的是lookupIterator.hasNext(),
public boolean hasNext() {if (acc == null) {return hasNextService();} else {PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {public Boolean run() { return hasNextService(); }};return AccessController.doPrivileged(action, acc);}}
当 acc ==null 时,
private boolean hasNextService() {if (nextName != null) {return true;}if (configs == null) {try {String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = 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;}
当我们进行driversIterator.next();时,等同在调用
public S next() {if (acc == null) {return nextService();} else {PrivilegedAction<S> action = new PrivilegedAction<S>() {public S run() { return nextService(); }};return AccessController.doPrivileged(action, acc);}}
当 acc==null 时。最关键的逻辑在这里,Class<?> c = Class.forName(cn, false, loader);,然后S p = service.cast(c.newInstance()); 并放入到providers.put(cn, p);
private S nextService() {if (!hasNextService())throw new NoSuchElementException();String cn = nextName;nextName = null;Class<?> c = null;try {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());providers.put(cn, p);return p;} catch (Throwable x) {fail(service,"Provider " + cn + " could not be instantiated",x);}throw new Error(); // This cannot happen}
使用场景
JDBC 源码补充
jdk 的 rt 包下Driver.class是个接口,全类型限定为java.sql.Driver:结构如下:
第三方mysql-connector-java-8.0.27.jar 下的 Driver 实现了 java.sql.Driver接口。代码如下:
package com.mysql.cj.jdbc;import java.sql.DriverManager;
import java.sql.SQLException;public class Driver extends NonRegisteringDriver implements java.sql.Driver {public Driver() throws SQLException {}static {try {DriverManager.registerDriver(new Driver());} catch (SQLException var1) {throw new RuntimeException("Can't register driver!");}}
}
第三方mysql-connector-java-8.0.27.jar 的 META-INF/serivces下暴露接口文件,供 SPI 机制发现使用。
使用方通过 ServiceLoader 发现所有的接口实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
SPI打破双亲委派机制
父加载器委托子加载器加载,打破了双亲委派机制。
- 由于 rt 包的类加载是 bootstrap classloader
- rt 包中DriverManager在loadInitialDrivers中,使用到了 ServiceLoader 触发 Driver.class的加载
- 先加载 java.sql.Driver,此时使用到的是 bootstrap classloader
- 然后ClassLoader cl = Thread.currentThread().getContextClassLoader();此时的上下文加载器为 App Classloader。
这种父类加载器委托子类加载的行为边打破了双亲委派机制
参考
https://blog.csdn.net/chen462488588/article/details/123894418
相关文章:
SPI 动态服务发现机制
SPI(Service Provier Interface)是一种服务发现机制,通过ClassPath下的META—INF/services文件查找文件,自动加载文件中定义的类,再调用forName加载; spi可以很灵活的让接口和实现分离, 让API提…...
【C++进阶07】哈希表and哈希桶
一、哈希概念 顺序结构以及平衡树中 元素关键码与存储位置没有对应关系 因此查找一个元素 必须经过关键码的多次比较 顺序查找时间复杂度为O(N) 平衡树中为树的高度,即O( l o g 2 N log_2 N log2N) 搜索效率 搜索过程中元素的比较次数 理想的搜索方法:…...
Go 语言实现冒泡排序算法的简单示例
以下是使用 Go 语言实现冒泡排序算法的简单示例: package mainimport "fmt"func bubbleSort(arr []int) {n : len(arr)for i : 0; i < n-1; i {for j : 0; j < n-i-1; j {if arr[j] > arr[j1] {// 交换元素arr[j], arr[j1] arr[j1], arr[j]}}}…...
JAVA 学习 面试(五)IO篇
BIO是阻塞I/O,NIO是非阻塞I/O,AIO是异步I/O。BIO每个连接对应一个线程,NIO多个连接共享少量线程,AIO允许应用程序异步地处理多个操作。NIO,通过Selector,只需要一个线程便可以管理多个客户端连接࿰…...
vue3相比vue2的效率提升
1、静态提升 2、预字符串化 3、缓存事件处理函数 4、Block Tree 5、PatchFlag 一、静态提升 在vue3中的app.vue文件如下: 在服务器中,template中的内容会变异成render渲染函数。 最终编译后的文件: 1.静态节点优化 那么这里为什么是两部分…...
web terminal - 如何在mac os上运行gotty
gotty可以让你使用web terminal的方式与环境进行交互,实现终端效果 假设你已经配置好了go环境,首先使用go get github.com/yudai/gotty命令获取可执行文件,默认会安装在$GOPATH/bin这个目录下,注意如果你的go版本比较高ÿ…...
机械设计-哈工大课程学习-螺纹连接
圆柱螺纹主要几何参数螺纹参数 ①外径(大径),与外螺纹牙顶或内螺纹牙底相重合的假想圆柱体直径。螺纹的公称直径即大径。 ②内径(小径),与外螺纹牙底或内螺纹牙顶相重合的假想圆柱体直径。 ③中径ÿ…...
ai绘画|stable diffusion的发展史!简短易懂!!!
手把手教你入门绘图超强的AI绘画,用户只需要输入一段图片的文字描述,即可生成精美的绘画。给大家带来了全新保姆级教程资料包 (文末可获取) 一、stable diffusion的发展史 本文目标:学习交流 对于熟悉SD的同学&#x…...
水塘抽样算法
水塘抽样算法 1、问题描述 最近经常能看到面经中出现在大数据流中的随机抽样问题 即:当内存无法加载全部数据时,如何从包含未知大小的数据流中随机选取k个数据,并且要保证每个数据被抽取到的概率相等。 假设数据流含有N个数,我…...
easyui渲染隐藏域<input type=“hidden“ />为textbox可作为分割条使用
最近在修改前端代码的时候,偶然发现使用javascript代码渲染的方式将<input type"hidden" />渲染为textbox时,会显示一个神奇的效果,这个textbox输入框并不会隐藏,而是显示未一个细条,博主发现非常适合…...
100天精通Python(实用脚本篇)——第113天:基于Tesseract-OCR实现OCR图片文字识别实战
文章目录 专栏导读1. OCR技术介绍2. 模块介绍3. 模块安装4. 代码实战4.1 英文图片测试4.2 数字图片测试4.3 中文图片识别 书籍分享 专栏导读 🔥🔥本文已收录于《100天精通Python从入门到就业》:本专栏专门针对零基础和需要进阶提升的同学所准…...
Go七天实现RPC
0.前言 本文是学习自7天用Go从零实现RPC框架GeeRPC | 极客兔兔 在此基础上,加入自己的学习过程与理解。 1.RPC 框架 RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,允许调用不同进程空间的程序。RPC 的客户端和服务器可以…...
Elasticsearch:和 LIamaIndex 的集成
LlamaIndex 是一个数据框架,供 LLM 应用程序摄取、构建和访问私有或特定领域的数据。 LlamaIndex 是开源的,可用于构建各种应用程序。 在 GitHub 上查看该项目。 安装 在 Docker 上设置 Elasticsearch 使用以下 docker 命令启动单节点 Elasticsearch 实…...
QT基础篇(14)QT操作office实例
1.QT操作office的基本方式 通过QT操作Office软件,可以使用Qt的QAxObject类来进行操作。下面是一个例子,展示了通过Qt操作Excel的基本方式: #include <QApplication> #include <QAxObject>int main(int argc, char *argv[]) {QA…...
重拾计网-第四弹 计算机网络性能指标
ps:本文章的图片内容来源都是来自于湖科大教书匠的视频,声明:仅供自己复习,里面加上了自己的理解 这里附上视频链接地址:1.5 计算机网络的性能指标(1)_哔哩哔哩_bilibili 目录 &#x…...
【Vue】Vue 路由的配置及使用
目录捏 前言一、路由是什么?1.前端路由2.后端路由 二、路由配置1.安装路由2.配置路由 三、路由使用1.route 与 router2. 声明式导航3. 指定组件的呈现位置 四、嵌套路由(多级路由)五、路由重定向1.什么是路由重定向?2.设置 redire…...
网络安全事件分级指南
一、特别重大网络安全事件 符合下列情形之一的,为特别重大网络安全事件: 1.重要网络和信息系统遭受特别严重的系统损失,造成系统大面积瘫痪,丧失业务处理能力。 2.国家秘密信息、重要敏感信息、重要数据丢失或被窃取、篡改、假…...
uniapp组件库SwipeAction 滑动操作 使用方法
目录 #平台差异说明 #基本使用 #修改按钮样式 #点击事件 #API #Props #Event 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作。 注意 如果把该组件通过v-for用于左滑删除的列表,请保证循环的:key是一个唯一值,可以…...
YARN节点故障的容错方案
YARN节点故障的容错方案 1. RM高可用1.1 选主和HA切换逻辑 2. NM高可用2.1 感知NM节点异常2.2 异常NM上的任务处理 4. 疑问和思考4,1 RM感知NM异常需要10min,对于app来说是否太长了? 5. 参考文档 本文主要探讨yarn集群的高可用容错方案和容错能力的探讨。…...
C++后端笔记
C后端笔记 资源整理一、高级语言程序设计1.1 进制1.2 程序结构基本知识1.3 数据类型ASCII码命名规则变量间的赋值浮点型变量的作用字符变量常变量 const运算符 二、高级语言程序设计(荣) 资源整理 C后端开发学习路线及推荐学习时间 C基础知识大全 C那…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
