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

【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 工作原理

  1. 定义服务接口:首先定义一个服务接口。
  2. 实现服务接口:编写多个实现该接口的类。
  3. 配置文件:在实现类的JAR包中,META-INF/services/目录下创建一个以服务接口全限定名为文件名的文件,文件内容是实现类的全限定名。
  4. 加载服务提供者:使用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();}}
}

关键点解释

  1. 构造函数

    • ServiceLoader 的构造函数私有化,防止外部直接实例化。
    • 构造函数接收服务接口类型、类加载器和配置文件的枚举。
  2. 静态方法 load

    • 用于获取 ServiceLoader 的实例。
    • 调用 loadConfigurations 方法加载配置文件。
  3. 静态方法 loadConfigurations

    • 根据服务接口类型和类加载器,加载 META-INF/services/ 目录下的配置文件。
    • 返回配置文件的枚举。
  4. 方法 iterator

    • 返回一个迭代器,用于遍历服务提供者。
  5. 内部类 LazyIterator

    • 实现了 Iterator 接口,用于懒加载服务提供者。
    • 构造函数初始化服务接口类型、类加载器和配置文件的枚举。
    • loadNextIterator 方法从配置文件中读取类名并加载相应的类。
    • parse 方法解析配置文件中的类名。
    • hasNextnext 方法分别用于检查是否有下一个服务提供者和返回下一个服务提供者。

使用示例

以下是一个使用 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. 定义服务接…...

多模态技术的协同表现:从文本生成、语音合成到口型同步综合测评

本文是针对多模态对话系统核心技术栈的使用效果和网络测评整理。 测评内容基于用户体验&#xff0c;侧重于从使用者角度出发&#xff0c;讨论实际操作中的体验感受&#xff0c;如技术的易用性、输出效果如文本的连贯性、语音的自然度、口型同步的准确性等。不涉及具体算法架构…...

Java最全面试题->Java主流框架->Srping面试题

Spring面试题 下边是我自己整理的面试题,基本已经很全面了,想要的可以私信我,我会不定期去更新思维导图 哪里不会点哪里 谈谈你对 Spring 的理解? Spring 是一个开源框架,为简化企业级应用开发而生。Spring 可以是使简单的 JavaBean 实现以前只有 EJB 才能实现的功能。…...

参编国家标准需要注意的事项有哪些?

1. 项目相关性&#xff1a; • 选择与自身企业产品、业务或专业领域紧密相关的国家标准进行参编。这样不仅能确保企业在标准制定过程中发挥自身的优势和专长&#xff0c;使参编工作更有实际意义和价值&#xff0c;也有利于企业将标准更好地应用于自身的生产经营活动&#xff0c…...

【Dash】feffery_antd_components 按钮组件的应用

一、feffery_antd_componenet 中的 AntdFloatButton 和 AntdFloatButtonGroup AntdFloatButton 和 AntdFloatButtonGroup 是两个用于创建悬浮按钮和悬浮按钮组的组件。 AntdFloatButton 是单个悬浮按钮组件&#xff0c;它提供了多种属性来定义按钮的外观及行为。AntdFloatBut…...

01 springboot-整合日志(logback-config.xml)

logback-config.xml 是一个用于配置 Logback 日志框架的 XML 文件&#xff0c;通常位于项目的 classpath 下的根目录或者 src/main/resources 目录下。 Logback 提供了丰富的配置选项&#xff0c;可以满足各种不同的日志需求。需要根据具体情况进行配置。 项目创建&#xff0…...

Java最全面试题->计算机基础面试题->计算机网络面试题

计算机网络 下边是我自己整理的面试题&#xff0c;基本已经很全面了&#xff0c;想要的可以私信我&#xff0c;我会不定期去更新思维导图 哪里不会点哪里 1.说一下TCP/IP四层模型 TCP/IP协议是美国国防部高级计划研究局为实现ARPANET互联网而开发的。 网络接口层&#xff…...

VSCode编译器改为中文

1. 通过快捷键设置中文 打开命令面板&#xff1a;按住键盘上的CtrlShiftP组合键&#xff0c;打开命令面板。 输入并设置语言&#xff1a;在命令面板中输入Configure Display Language。 点击Configure Display Language选项。 在弹出的语言选择列表中&#xff0c;选择zh-cn…...

前端开发设计模式——状态模式

目录 一、状态模式的定义和特点 二、状态模式的结构与原理 1.结构&#xff1a; 2.原理&#xff1a; 三、状态模式的实现方式 四、状态模式的使用场景 1.按钮的不同状态&#xff1a; 2.页面加载状态&#xff1a; 3.用户登录状态&#xff1a; 五、状态模式的优点 1.提…...

特种作业操作烟花爆竹试题分享

1.&#xff08;单选题&#xff09;职业卫生研究的是人类从事各种职业劳动过程中的&#xff08; &#xff09;。 A.健康问题 B.环境问题 C.卫生问题 答案:C 2.&#xff08;单选题&#xff09;安全生产事关人民群众的&#xff08; &#xff09;安全&#xff0c;事关改革发展和…...

实现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项目中&#xff0c;确保定时任务只执行一次是一个常见的需求。这种需求可以通过多种方式来实现&#xff0c;以下是一些常见的方法&#xff0c;它们各具特点&#xff0c;可以根据项目的实际需求来选择最合适的方法。 1. 使用Scheduled注解并设置极大延迟 一种简单…...

【Python数据可视化】利用Matplotlib绘制美丽图表!

【Python数据可视化】利用Matplotlib绘制美丽图表&#xff01; 数据可视化是数据分析过程中的重要步骤&#xff0c;它能直观地展示数据的趋势、分布和相关性&#xff0c;帮助我们做出明智的决策。在 Python 中&#xff0c;Matplotlib 是最常用的可视化库之一&#xff0c;它功能…...

【最新通知】2024年Cisco思科认证CCNA详解

CCNA现在涵盖安全性、自动化和可编程性。该计划拥有一项涵盖IT职业基础知识的认证&#xff0c;包括一门考试和一门培训课程&#xff0c;助您做好准备。 CCNA培训课程和考试最近面向最新技术和工作岗位进行了重新调整&#xff0c;为您提供了向任何方向发展事业所需的基础。CCNA认…...

监控内容、监控指标、监控工具大科普

在现代信息技术领域&#xff0c;监控技术扮演着至关重要的角色。它帮助我们实时了解系统、网络、应用以及环境的状态&#xff0c;确保它们的安全、稳定和高效运行。以下是对监控内容、监控指标和监控工具的详细科普。 一、监控内容 监控内容是指监控系统所关注和记录的具体信…...

生成文件夹 - python 实现

生成文件夹保存图片和文本等信息。 代码具体实现如下&#xff1a; #-*-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 提供的常用快捷键

&#x1f600;前言 本篇博文是关于python的基础语言介绍及IDLE 提供的常用快捷键&#xff0c;希望你能够喜欢 &#x1f3e0;个人主页&#xff1a;晨犀主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是晨犀&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的…...

【python】OpenCV—Sort the Point Set from Top Left to Bottom Right

文章目录 1、功能描述2、代码实现3、效果展示4、更多例子5、参考 1、功能描述 给出一张图片&#xff0c;里面含有各种图形&#xff0c;取各种图形的中心点&#xff0c;从左到右从上到下排序 例如 2、代码实现 import cv2 import numpy as npdef process_img(img):img_gray c…...

LeetCode 1493.删掉一个元素以后全为1的最长子数组

题目&#xff1a; 给你一个二进制数组 nums &#xff0c;你需要从中删掉一个元素。 请你在删掉元素的结果数组中&#xff0c;返回最长的且只包含 1 的非空子数组的长度。 如果不存在这样的子数组&#xff0c;请返回 0 。 思路&#xff1a;不定长滑动窗口&#xff0c;将问题…...

php常用设计模式之工厂模式

引言 在日常开发中&#xff0c;我们一些业务场景需要用到发送短信通知。然而实际情况考虑到不同厂商之间的价格、实效性、可能会出现的情况等 我们的业务场景往往会接入多个短信厂商来保证我们业务的正常运行&#xff0c;而不同的短信厂商&#xff08;如阿里云短信、腾讯云短信…...

通用软件版本标识

软件版本标识&#xff1a;了解不同的版本类型 在软件开发和发布过程中&#xff0c;版本号和标识扮演着重要的角色。它们不仅帮助开发者追踪软件的演变&#xff0c;还让用户了解软件的稳定性和功能。以下是一些常见的软件版本标识&#xff0c;以及它们的含义和用途。 Alpha&am…...

(计算机毕设)基于SpringBoot的就业平台开题报告

一、立题依据(国内外研究进展或选题背景、研究意义等) 国内外研究进展或选题背景 在全球化的大背景下&#xff0c;就业问题一直是各国政府和社会各界关注的焦点。随着互联网技术的普及和发展&#xff0c;网络招聘已成为求职者和企业招聘的主要渠道。据相关数据显示&#xff0…...

STM32G4系列MCU的ADC模块标定方法和采样时间

目录 概述 1 ADC模块标定 1.1 功能介绍 1.2 软件程序校准ADC 1.2.1 标定步骤 1.2.2 标定时序框图 1.3 软件程序重新注入校准因子到ADC 1.3.1 标定步骤 1.3.2 更新ADC校准因子 1.4 用单个ADC转换单端和差分模拟输入 1.4.1 标定流程 1.4.2 混合单端和差分通道 2 通道…...

NVIDIA Jetson支持的神经网络加速的量化平台

NVIDIA Jetson支持的神经网络加速的量化工具、技术 NVIDIA Jetson 是专为边缘计算和嵌入式系统设计的高性能计算平台&#xff0c;它支持多种深度学习模型的部署和推理。对于神经网络加速的量化平台&#xff0c;Jetson 支持以下技术和工具&#xff1a; TensorRT&#xff1a;Ten…...

MySQL 免密登录的几种配置方式

文章目录 MySQL 免密登录的几种配置方式使用操作系统用户实现免密登录具体步骤&#xff1a;Step 1: 修改 MySQL 配置文件Step 2: 重启 MySQL 服务Step 3: 使用系统用户登录 MySQL优点&#xff1a;缺点&#xff1a; 使用 mysql_config_editor 配置免密文件具体步骤&#xff1a;S…...

html全局属性、框架标签

常用的全局属性&#xff1a; 属性名含义id 给标签指定唯一标识&#xff0c;注意&#xff1a;id是不能重复的。 作用&#xff1a;可以让label标签与表单控件相关联&#xff1b;也可以与css、JavaScript配合使用。 注意&#xff1a;不能再以下HTML元素中使用&#xff1a;<hea…...

ARL 灯塔 | CentOS7 — ARL 灯塔搭建流程(Docker)

关注这个工具的其它相关内容&#xff1a;自动化信息收集工具 —— ARL 灯塔使用手册 - CSDN 博客 灯塔&#xff0c;全称&#xff1a;ARL 资产侦察灯塔系统&#xff0c;有着域名资产发现和整理、IP/IP 段资产整理、端口扫描和服务识别、WEB 站点指纹识别、资产分组管理和搜索等等…...

抖音列表页采集-前言

准备工作&#xff1a; 1.关于selenium介绍&#xff1a; python自动化入门的话&#xff0c;selenium绝对是最方便的选择&#xff0c;基本逻辑即为&#xff1a;程序模拟人的行为操作浏览器&#xff0c;这样的操作需要借用浏览器驱动&#xff0c;我选用的是chrome浏览器&#xff…...

Linux 端口占用 kill被占用的端口 杀掉端口

1、yum install lsof 2、输入netstat -tln,查看系统当前所有被占用端口 3、根据端口查询进程,输入lsof -i :9555,切记不要忘了添加冒号 4、 既然知道进程号了,那杀死当前进程就简单多了,直接 kill -9 PID 回车...

爬虫之数据解析

数据解析 数据解析这篇内容, 很多知识涉及到的都是以前学习过的内容了, 那这篇文章我们主要以实操为主, 来展开来讲解关于数据解析的内容。 360搜索图片 请求的url大家不需要再找了, 相信大家都会找请求了, 寻找请求从我的第一篇爬虫的博客开始到现在一直都在写,这边的话, 我已…...