【Java知识】Java进阶-服务发现机制SPI
文章目录
- SPI概述
- SPI 工作原理
- ServiceLoader代码展示
- 简化的 `ServiceLoader` 类
- 关键点解释
- 使用示例
- 1. 定义服务接口
- 2. 实现服务提供者
- 3. 配置文件
- 4. 加载服务提供者
- 总结
- SPI使用场景
- 1. 数据库驱动
- 2. 日志框架
- 3. 图像处理
- 4. 加密算法
- 5. 插件系统
- 6. 缓存机制
- 示例代码
- 1. 定义服务接口
- 2. 实现服务提供者
- 3. 配置文件
- 4. 加载服务提供者
- 总结
SPI概述
Java的SPI(Service Provider Interface)是一种服务发现机制,用于定义服务提供者和服务使用者之间的接口。通过SPI,开发者可以在运行时动态地加载和使用实现了特定接口的服务实现类。这种机制常用于框架与插件化开发中,使得框架可以灵活地支持多种实现而无需修改代码。
SPI 工作原理
- 定义服务接口:首先定义一个服务接口。
- 实现服务接口:编写多个实现该接口的类。
- 配置文件:在实现类的JAR包中,
META-INF/services/目录下创建一个以服务接口全限定名为文件名的文件,文件内容是实现类的全限定名。 - 加载服务提供者:使用
ServiceLoader类加载服务提供者。
ServiceLoader代码展示
当然!ServiceLoader 是 Java 中用于加载服务提供者的工具类。下面是 ServiceLoader 的核心代码及其注释说明。为了更好地理解,我们将展示一个简化的版本,重点在于关键的方法和逻辑。
简化的 ServiceLoader 类
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.StreamSupport;public final class ServiceLoader<S> implements Iterable<S> {private final Class<S> service; // 服务接口类型private final ClassLoader loader; // 类加载器private final Enumeration<URL> configs; // 配置文件的枚举private final Iterator<S> providers; // 服务提供者的迭代器// 构造函数private ServiceLoader(Class<S> svc, ClassLoader cl, Enumeration<URL> configs) {this.service = svc;this.loader = cl;this.configs = configs;this.providers = new LazyIterator(svc, cl, configs);}// 获取 ServiceLoader 的实例public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {return new ServiceLoader<>(service, loader, loadConfigurations(service, loader));}// 加载配置文件private static <S> Enumeration<URL> loadConfigurations(Class<S> service, ClassLoader loader) {String fullName = "META-INF/services/" + service.getName();try {return loader.getResources(fullName);} catch (IOException x) {throw new ServiceConfigurationError(service.getName() + ": " + x, x);}}// 返回一个迭代器,用于遍历服务提供者@Overridepublic Iterator<S> iterator() {return providers;}// 内部类:懒加载迭代器private static class LazyIterator<S> implements Iterator<S> {private final Class<S> service; // 服务接口类型private final ClassLoader loader; // 类加载器private final Enumeration<URL> configs; // 配置文件的枚举private Iterator<S> nextIterator; // 下一个迭代器private LazyIterator(Class<S> service, ClassLoader loader, Enumeration<URL> configs) {this.service = service;this.loader = loader;this.configs = configs;this.nextIterator = loadNextIterator();}// 加载下一个迭代器private Iterator<S> loadNextIterator() {if (!configs.hasMoreElements()) {return null;}URL url = configs.nextElement();try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {return parse(reader);} catch (IOException x) {throw new ServiceConfigurationError(service.getName() + ": " + x, x);}}// 解析配置文件中的类名private Iterator<S> parse(BufferedReader reader) throws IOException {StringBuilder className = new StringBuilder();while (reader.ready()) {int ch = reader.read();if (ch == '#' || ch == '\n' || ch == '\r') {if (className.length() > 0) {break;}continue;}if (Character.isWhitespace((char) ch)) {continue;}className.append((char) ch);}if (className.length() == 0) {return null;}String providerClassName = className.toString();try {Class<?> providerClass = Class.forName(providerClassName, true, loader);if (!service.isAssignableFrom(providerClass)) {throw new ServiceConfigurationError(service.getName() + ": " + providerClassName + " not a subtype");}return Collections.singleton((S) providerClass.getDeclaredConstructor().newInstance()).iterator();} catch (Exception x) {throw new ServiceConfigurationError(service.getName() + ": " + x, x);}}// 返回下一个服务提供者@Overridepublic boolean hasNext() {if (nextIterator == null) {return false;}if (!nextIterator.hasNext()) {nextIterator = loadNextIterator();}return nextIterator != null && nextIterator.hasNext();}@Overridepublic S next() {if (!hasNext()) {throw new NoSuchElementException();}return nextIterator.next();}}
}
关键点解释
-
构造函数:
ServiceLoader的构造函数私有化,防止外部直接实例化。- 构造函数接收服务接口类型、类加载器和配置文件的枚举。
-
静态方法
load:- 用于获取
ServiceLoader的实例。 - 调用
loadConfigurations方法加载配置文件。
- 用于获取
-
静态方法
loadConfigurations:- 根据服务接口类型和类加载器,加载
META-INF/services/目录下的配置文件。 - 返回配置文件的枚举。
- 根据服务接口类型和类加载器,加载
-
方法
iterator:- 返回一个迭代器,用于遍历服务提供者。
-
内部类
LazyIterator:- 实现了
Iterator接口,用于懒加载服务提供者。 - 构造函数初始化服务接口类型、类加载器和配置文件的枚举。
loadNextIterator方法从配置文件中读取类名并加载相应的类。parse方法解析配置文件中的类名。hasNext和next方法分别用于检查是否有下一个服务提供者和返回下一个服务提供者。
- 实现了
使用示例
以下是一个使用 ServiceLoader 的简单示例:
1. 定义服务接口
public interface Logger {void log(String message);
}
2. 实现服务提供者
public class ConsoleLogger implements Logger {@Overridepublic void log(String message) {System.out.println("CONSOLE: " + message);}
}public class FileLogger implements Logger {@Overridepublic void log(String message) {try (PrintWriter writer = new PrintWriter(new FileWriter("log.txt", true))) {writer.println("FILE: " + message);} catch (IOException e) {e.printStackTrace();}}
}
3. 配置文件
在项目的 src/main/resources/META-INF/services/ 目录下创建一个文件,文件名为 com.example.Logger,文件内容如下:
com.example.ConsoleLogger
com.example.FileLogger
4. 加载服务提供者
import java.util.ServiceLoader;
import java.util.Iterator;public class Main {public static void main(String[] args) {// 使用ServiceLoader加载Logger接口的所有实现ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);// 遍历所有实现Iterator<Logger> it = loader.iterator();while (it.hasNext()) {Logger logger = it.next();logger.log("This is a test message.");}}
}
总结
通过上述代码和解释,你可以看到 ServiceLoader 如何通过配置文件动态加载和使用服务提供者。这种机制使得应用程序可以更加灵活地管理和扩展功能,特别适用于需要支持多种实现的场景。希望这些示例和解释能帮助你更好地理解和使用 ServiceLoader。如果有任何问题或需要进一步的帮助,请随时提问!
SPI使用场景
Java的SPI(Service Provider Interface)机制主要用于在运行时动态加载和使用服务提供者。这种机制使得应用程序可以在不修改代码的情况下,灵活地切换和扩展功能。以下是SPI的一些常见使用场景:
1. 数据库驱动
场景描述:Java应用程序需要连接不同的数据库(如MySQL、PostgreSQL、Oracle等),并且希望能够轻松地切换数据库而不需要修改大量代码。
SPI实现:
- 服务接口:定义一个通用的数据库连接接口。
- 服务提供者:每个数据库驱动都实现这个接口,并在
META-INF/services/java.sql.Driver文件中声明自己。 - 服务加载:应用程序使用
ServiceLoader动态加载并使用相应的数据库驱动。
2. 日志框架
场景描述:应用程序希望支持多种日志框架(如Log4j、SLF4J、java.util.logging等),并且能够在运行时选择不同的日志框架。
SPI实现:
- 服务接口:定义一个通用的日志接口。
- 服务提供者:每个日志框架实现这个接口,并在
META-INF/services/com.example.Logger文件中声明自己。 - 服务加载:应用程序使用
ServiceLoader动态加载并使用相应的日志框架。
3. 图像处理
场景描述:图像处理应用程序需要支持多种图像格式(如JPEG、PNG、GIF等),并且能够动态加载和使用不同的图像处理器。
SPI实现:
- 服务接口:定义一个通用的图像处理器接口。
- 服务提供者:每个图像格式的处理器实现这个接口,并在
META-INF/services/com.example.ImageProcessor文件中声明自己。 - 服务加载:应用程序使用
ServiceLoader动态加载并使用相应的图像处理器。
4. 加密算法
场景描述:安全应用程序需要支持多种加密算法(如AES、RSA、DES等),并且能够在运行时选择不同的加密算法。
SPI实现:
- 服务接口:定义一个通用的加密算法接口。
- 服务提供者:每个加密算法实现这个接口,并在
META-INF/services/com.example.EncryptionAlgorithm文件中声明自己。 - 服务加载:应用程序使用
ServiceLoader动态加载并使用相应的加密算法。
5. 插件系统
场景描述:应用程序希望支持插件化开发,允许用户在运行时动态添加和卸载插件。
SPI实现:
- 服务接口:定义一个通用的插件接口。
- 服务提供者:每个插件实现这个接口,并在
META-INF/services/com.example.Plugin文件中声明自己。 - 服务加载:应用程序使用
ServiceLoader动态加载并使用相应的插件。
6. 缓存机制
场景描述:分布式系统需要支持多种缓存机制(如Redis、Memcached、Caffeine等),并且能够在运行时选择不同的缓存实现。
SPI实现:
- 服务接口:定义一个通用的缓存接口。
- 服务提供者:每个缓存实现这个接口,并在
META-INF/services/com.example.Cache文件中声明自己。 - 服务加载:应用程序使用
ServiceLoader动态加载并使用相应的缓存实现。
示例代码
以下是一个简单的SPI使用示例,展示了如何定义服务接口、实现服务提供者,并使用 ServiceLoader 加载服务提供者。
1. 定义服务接口
// Logger.java
public interface Logger {void log(String message);
}
2. 实现服务提供者
// ConsoleLogger.java
public class ConsoleLogger implements Logger {@Overridepublic void log(String message) {System.out.println("CONSOLE: " + message);}
}// FileLogger.java
public class FileLogger implements Logger {@Overridepublic void log(String message) {try (PrintWriter writer = new PrintWriter(new FileWriter("log.txt", true))) {writer.println("FILE: " + message);} catch (IOException e) {e.printStackTrace();}}
}
3. 配置文件
在项目的 src/main/resources/META-INF/services/ 目录下创建一个文件,文件名为 com.example.Logger,文件内容如下:
com.example.ConsoleLogger
com.example.FileLogger
4. 加载服务提供者
// Main.java
import java.util.ServiceLoader;
import java.util.Iterator;public class Main {public static void main(String[] args) {// 使用ServiceLoader加载Logger接口的所有实现ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);// 遍历所有实现Iterator<Logger> it = loader.iterator();while (it.hasNext()) {Logger logger = it.next();logger.log("This is a test message.");}}
}
总结
SPI机制使得Java应用程序能够更加灵活地管理和使用服务提供者。通过定义服务接口、实现服务提供者,并使用 ServiceLoader 加载服务提供者,可以在运行时动态地选择和切换不同的实现。这种机制特别适用于需要高度可扩展性和灵活性的应用场景。希望这些示例和解释能帮助你更好地理解和使用SPI机制。如果有任何问题或需要进一步的帮助,请随时提问!
相关文章:
【Java知识】Java进阶-服务发现机制SPI
文章目录 SPI概述SPI 工作原理 ServiceLoader代码展示简化的 ServiceLoader 类关键点解释使用示例1. 定义服务接口2. 实现服务提供者3. 配置文件4. 加载服务提供者 总结 SPI使用场景1. 数据库驱动2. 日志框架3. 图像处理4. 加密算法5. 插件系统6. 缓存机制示例代码1. 定义服务接…...
多模态技术的协同表现:从文本生成、语音合成到口型同步综合测评
本文是针对多模态对话系统核心技术栈的使用效果和网络测评整理。 测评内容基于用户体验,侧重于从使用者角度出发,讨论实际操作中的体验感受,如技术的易用性、输出效果如文本的连贯性、语音的自然度、口型同步的准确性等。不涉及具体算法架构…...
Java最全面试题->Java主流框架->Srping面试题
Spring面试题 下边是我自己整理的面试题,基本已经很全面了,想要的可以私信我,我会不定期去更新思维导图 哪里不会点哪里 谈谈你对 Spring 的理解? Spring 是一个开源框架,为简化企业级应用开发而生。Spring 可以是使简单的 JavaBean 实现以前只有 EJB 才能实现的功能。…...
参编国家标准需要注意的事项有哪些?
1. 项目相关性: • 选择与自身企业产品、业务或专业领域紧密相关的国家标准进行参编。这样不仅能确保企业在标准制定过程中发挥自身的优势和专长,使参编工作更有实际意义和价值,也有利于企业将标准更好地应用于自身的生产经营活动,…...
【Dash】feffery_antd_components 按钮组件的应用
一、feffery_antd_componenet 中的 AntdFloatButton 和 AntdFloatButtonGroup AntdFloatButton 和 AntdFloatButtonGroup 是两个用于创建悬浮按钮和悬浮按钮组的组件。 AntdFloatButton 是单个悬浮按钮组件,它提供了多种属性来定义按钮的外观及行为。AntdFloatBut…...
01 springboot-整合日志(logback-config.xml)
logback-config.xml 是一个用于配置 Logback 日志框架的 XML 文件,通常位于项目的 classpath 下的根目录或者 src/main/resources 目录下。 Logback 提供了丰富的配置选项,可以满足各种不同的日志需求。需要根据具体情况进行配置。 项目创建࿰…...
Java最全面试题->计算机基础面试题->计算机网络面试题
计算机网络 下边是我自己整理的面试题,基本已经很全面了,想要的可以私信我,我会不定期去更新思维导图 哪里不会点哪里 1.说一下TCP/IP四层模型 TCP/IP协议是美国国防部高级计划研究局为实现ARPANET互联网而开发的。 网络接口层ÿ…...
VSCode编译器改为中文
1. 通过快捷键设置中文 打开命令面板:按住键盘上的CtrlShiftP组合键,打开命令面板。 输入并设置语言:在命令面板中输入Configure Display Language。 点击Configure Display Language选项。 在弹出的语言选择列表中,选择zh-cn…...
前端开发设计模式——状态模式
目录 一、状态模式的定义和特点 二、状态模式的结构与原理 1.结构: 2.原理: 三、状态模式的实现方式 四、状态模式的使用场景 1.按钮的不同状态: 2.页面加载状态: 3.用户登录状态: 五、状态模式的优点 1.提…...
特种作业操作烟花爆竹试题分享
1.(单选题)职业卫生研究的是人类从事各种职业劳动过程中的( )。 A.健康问题 B.环境问题 C.卫生问题 答案:C 2.(单选题)安全生产事关人民群众的( )安全,事关改革发展和…...
实现prometheus+grafana的监控部署
直接贴部署用的文件信息了 kubectl label node xxx monitoringtrue 创建命名空间 kubectl create ns monitoring 部署operator kubectl apply -f operator-rbac.yml kubectl apply -f operator-dp.yml kubectl apply -f operator-crd.yml # 定义node-export kubectl app…...
确保Spring Boot定时任务只执行一次方案
在Spring Boot项目中,确保定时任务只执行一次是一个常见的需求。这种需求可以通过多种方式来实现,以下是一些常见的方法,它们各具特点,可以根据项目的实际需求来选择最合适的方法。 1. 使用Scheduled注解并设置极大延迟 一种简单…...
【Python数据可视化】利用Matplotlib绘制美丽图表!
【Python数据可视化】利用Matplotlib绘制美丽图表! 数据可视化是数据分析过程中的重要步骤,它能直观地展示数据的趋势、分布和相关性,帮助我们做出明智的决策。在 Python 中,Matplotlib 是最常用的可视化库之一,它功能…...
【最新通知】2024年Cisco思科认证CCNA详解
CCNA现在涵盖安全性、自动化和可编程性。该计划拥有一项涵盖IT职业基础知识的认证,包括一门考试和一门培训课程,助您做好准备。 CCNA培训课程和考试最近面向最新技术和工作岗位进行了重新调整,为您提供了向任何方向发展事业所需的基础。CCNA认…...
监控内容、监控指标、监控工具大科普
在现代信息技术领域,监控技术扮演着至关重要的角色。它帮助我们实时了解系统、网络、应用以及环境的状态,确保它们的安全、稳定和高效运行。以下是对监控内容、监控指标和监控工具的详细科普。 一、监控内容 监控内容是指监控系统所关注和记录的具体信…...
生成文件夹 - python 实现
生成文件夹保存图片和文本等信息。 代码具体实现如下: #-*-coding:utf-8-*- # date:2021-04-13 # Author: DataBall - XIAN # Function: 生成文件夹import os if __name__ "__main__":path "./dataset"if not os.path.exists(path): # 如果…...
快速了解学会python基础语言及IDLE 提供的常用快捷键
😀前言 本篇博文是关于python的基础语言介绍及IDLE 提供的常用快捷键,希望你能够喜欢 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的…...
【python】OpenCV—Sort the Point Set from Top Left to Bottom Right
文章目录 1、功能描述2、代码实现3、效果展示4、更多例子5、参考 1、功能描述 给出一张图片,里面含有各种图形,取各种图形的中心点,从左到右从上到下排序 例如 2、代码实现 import cv2 import numpy as npdef process_img(img):img_gray c…...
LeetCode 1493.删掉一个元素以后全为1的最长子数组
题目: 给你一个二进制数组 nums ,你需要从中删掉一个元素。 请你在删掉元素的结果数组中,返回最长的且只包含 1 的非空子数组的长度。 如果不存在这样的子数组,请返回 0 。 思路:不定长滑动窗口,将问题…...
php常用设计模式之工厂模式
引言 在日常开发中,我们一些业务场景需要用到发送短信通知。然而实际情况考虑到不同厂商之间的价格、实效性、可能会出现的情况等 我们的业务场景往往会接入多个短信厂商来保证我们业务的正常运行,而不同的短信厂商(如阿里云短信、腾讯云短信…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
自然语言处理——Transformer
自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效,它能挖掘数据中的时序信息以及语义信息,但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN,但是…...
