详细分析Java中的SPI机制(附Demo)

news2024/12/23 14:34:16

目录

  • 前言
  • 1. 基本知识
  • 2. Demo
  • 3. 解读源码

前言

相关的Java知识推荐阅读:

  1. java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)
  2. 【Java项目】实战CRUD的功能整理(持续更新)

1. 基本知识

SPI(Service Provider Interface) 是一种服务发现机制,允许 Java 应用程序动态地加载和使用服务实现

SPI 机制是 Java 提供的服务加载机制的一部分,可以在运行时找到和加载实现接口的服务提供者,而不需要在编译时将这些实现硬编码到代码中

基本的知识点如下:

  • 定义 SPI 接口:SPI 需要定义一个接口或抽象类,作为服务的规范

  • 提供服务的实现:创建实现该接口的具体服务实现类

  • 配置服务提供者: 在 META-INF/services/ 目录下创建一个文件,文件名为接口的完全限定名,文件内容是实现该接口的类的完全限定名

  • 加载服务实现:使用 ServiceLoader 类来动态加载服务实现

了解基本的知识点,结合Demo进行了解

2. Demo

定义服务接口:

public interface GreetingService {
    void greet(String name);
}

提供服务的实现类1:

public class EnglishGreetingService implements GreetingService {
    @Override
    public void greet(String name) {
        System.out.println("Hello, " + name);
    }
}

实现类2:

public class SpanishGreetingService implements GreetingService {
    @Override
    public void greet(String name) {
        System.out.println("Hola, " + name);
    }
}

配置服务提供者:

在 META-INF/services/ 目录下创建一个文件 com.example.GreetingService,内容为服务实现的完全限定名:

com.example.EnglishGreetingService
com.example.SpanishGreetingService

最终加载服务实现类:

import java.util.ServiceLoader;

public class GreetingServiceLoader {
    public static void main(String[] args) {
        // 使用 ServiceLoader 加载 GreetingService 接口的所有实现类
        ServiceLoader<GreetingService> loader = ServiceLoader.load(GreetingService.class);
        
        // 遍历所有加载的服务实现
        for (GreetingService service : loader) {
            // 调用服务实现的方法
            service.greet("码农研究僧");
        }
    }
}

执行截图如下:

在这里插入图片描述

之所以读取此处的配置文件,可以通过源码查看:

在这里插入图片描述

基本的运行机制如下:

  1. 编写服务接口:
    定义一个接口或抽象类,该接口定义了服务提供者需要实现的功能

  2. 实现服务接口:
    创建一个或多个类实现该服务接口
    这些类是服务的实际提供者

  3. 配置服务提供者:
    META-INF/services/ 目录下创建一个文件,文件名为接口的完全限定名(即接口的全类名)
    在文件中列出所有实现该接口的服务提供者的完全限定名,每行一个类名

  4. 运行时加载服务:
    4.1 当应用程序运行并调用 ServiceLoader.load(GreetingService.class) 时,ServiceLoader 会读取 META-INF/services/com.example.GreetingService 文件,获取所有服务实现的类名
    ServiceLoader 通过反射机制动态加载这些类,并创建它们的实例
    4.2 使用反射 (Class.forName()) 加载服务实现类,通过 Class.newInstance() 创建服务实例,将服务实例缓存到 providers 中

  5. 使用服务:
    使用 ServiceLoader 的 iterator() 方法遍历所有实现,并调用它们的方法

3. 解读源码

解读部分源码:

reload方法:

  • 清空缓存的服务提供者 providers
  • 重新创建一个 LazyIterator 实例,用于重新查找和加载服务实现
public void reload() {
    providers.clear();
    lookupIterator = new LazyIterator(service, loader);
}

parse方法:

  • parse() 方法读取并解析配置文件中的服务实现类名
  • 使用 BufferedReader 按行读取文件,并调用 parseLine() 处理每一行
  • 将有效的类名添加到 names 列表中,并返回一个迭代器
private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError {
    InputStream in = null;
    BufferedReader r = null;
    ArrayList<String> names = new ArrayList<>();
    try {
        in = u.openStream();
        r = new BufferedReader(new InputStreamReader(in, "utf-8"));
        int lc = 1;
        while ((lc = parseLine(service, u, r, lc, names)) >= 0);
    } catch (IOException x) {
        fail(service, "Error reading configuration file", x);
    } finally {
        try {
            if (r != null) r.close();
            if (in != null) in.close();
        } catch (IOException y) {
            fail(service, "Error closing configuration file", y);
        }
    }
    return names.iterator();
}

parseLine方法:

  • parseLine() 方法处理服务配置文件中的每一行
  • 忽略注释(以 # 开头的行)和空行
  • 验证类名是否合法,并将有效的类名添加到 names 列表
private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError {
    String ln = r.readLine();
    if (ln == null) {
        return -1;
    }
    int ci = ln.indexOf('#');
    if (ci >= 0) ln = ln.substring(0, ci);
    ln = ln.trim();
    int n = ln.length();
    if (n != 0) {
        if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
            fail(service, u, lc, "Illegal configuration-file syntax");
        int cp = ln.codePointAt(0);
        if (!Character.isJavaIdentifierStart(cp))
            fail(service, u, lc, "Illegal provider-class name: " + ln);
        for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
            cp = ln.codePointAt(i);
            if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
                fail(service, u, lc, "Illegal provider-class name: " + ln);
        }
        if (!providers.containsKey(ln) && !names.contains(ln))
            names.add(ln);
    }
    return lc + 1;
}

LazyIterator 内部类:

  • LazyIterator 负责延迟加载服务提供者
  • hasNextService() 方法查找和加载服务配置文件中的服务实现类
  • nextService() 方法使用反射创建服务实例,并返回它
  • hasNext() 和 next() 方法实现了 Iterator 接口,用于遍历服务提供者
private class LazyIterator implements Iterator<S> {
    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = null;

    private LazyIterator(Class<S> service, ClassLoader loader) {
        this.service = service;
        this.loader = loader;
    }

    private boolean hasNextService() {
        if (nextName != null) {
            return true;
        }
        if (configs == null) {
            try {
                String fullName = PREFIX + service.getName();
                if (loader == null)
                    configs = ClassLoader.getSystemResources(fullName);
                else
                    configs = 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;
    }

    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
    }

    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);
        }
    }

    public S next() {
        return nextService();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1975009.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PANDA:免微调提升大型语言模型领域特定能力的新方法

人工智能咨询培训老师叶梓 转载标明出处 大模型虽然在广泛的任务上具有通用性&#xff0c;但在面对特定领域的任务时&#xff0c;它们的性能往往不如专门为这些领域训练的模型。传统的知识蒸馏&#xff08;Knowledge Distillation, KD&#xff09;方法通过资源密集型的微调过程…

怎么在电脑上查找打印机打印过的文件?告别翻箱倒柜!电脑查找已打印文件技巧公示!

在日常办公中&#xff0c;我们经常会使用打印机来输出各种文件&#xff0c;但有时候&#xff0c;我们可能需要回顾或查找之前打印过的文件。然而&#xff0c;这些文件一旦打印完成&#xff0c;往往就离开了我们的电脑屏幕&#xff0c;进入了纸质世界&#xff0c;而电子文件可能…

Tree-of-Traversals:结合知识图谱与大模型,通过树遍历和回溯寻找高置信度推理路径

Tree-of-Traversals&#xff1a;结合知识图谱与大模型&#xff0c;通过树遍历和回溯寻找高置信度推理路径 Tree-of-Traversals算法解析对比 MindMap1. 与知识图谱&#xff08;KGs&#xff09;的整合2. 推理方法3. 灵活性与可扩展性4. 在医学诊断中的应用 速度和准确1. 速度2. 推…

数据结构第九讲:二叉树

数据结构第九讲&#xff1a;二叉树 1.实现链式结构二叉树1.1二叉树的节点结构1.2创建二叉树节点1.3前中后序遍历1.3.1前序遍历1.3.2中序遍历1.3.3后序遍历1.3.4总结 1.4二叉树结点的个数1.4.1错误示范1.4.2实现方法 1.5二叉树叶子结点的个数1.6二叉树第k层结点的个数1.7二叉树的…

看门狗应用编程-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

看门狗应用编程 看门狗应用编程介绍 看门狗定时器的基本概念 看门狗是一个可以在一定时间内被复位/重置的计数器 如果在规定时间内没有复位&#xff0c;看门狗计时器溢出会对CPU产生复位信号使系统重启 有些看门狗可以只产生中断信号而不会使系统复位 I.MX6UL/I.MX6ULL So…

如何减少内存碎片的产生——页

文章目录 1 页的设计目的2 进程块和主存块的对应关系3 页、页框、页表3.1 页&#xff08;Page&#xff09;3.2 页框&#xff08;Page Frame&#xff09;3.3 页表&#xff08;Page Table&#xff09; 4 逻辑地址到物理地址的转换4.1 转换过程4.2 具体示例4.3 图示 参考资料封面 …

C语言程序设计25

《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 习题2.2 分析下面程序的运行结果&#xff0c;然后上机验证。 代码&#xff1a; //《C程序设计教程&#xff08;第四版&#xff09;——谭浩强》 //习题2.2 分析下面程序的运行结果&#xff0c;然后上机验证。#inc…

【C语言篇】操作符详解(下篇)

文章目录 操作符详解&#xff08;下篇&#xff09;前言条件操作符移位操作符左移操作符右移操作符 位操作符下标引用操作符函数调用操作符 操作符的属性&#xff1a;优先级和结合性优先级结合性表达式求值整形提升算术转换 操作符详解&#xff08;下篇&#xff09; 前言 操作…

JavaScript基础——JavaScript常见语句(判断语句、循环语句、控制流语句)

JavaScript提供了丰富的语句来控制程序的执行流程&#xff0c;包括用于条件判断的if、switch和三元运算符&#xff0c;以及用于循环的for、while、do...while、for...in和for...of。此外&#xff0c;还有控制流语句如break、continue和return。 判断语句 if 语句 if 语句&…

C/C++开发,opencv轮廓提取实现

一、cv::findContours轮廓提取函数 1.1 cv::findContours函数简介 cv::findContours 函数是用于从二值图像&#xff08;灰度图&#xff09;中检索轮廓。这个函数在OpenCV的不同版本中参数可能有所不同&#xff0c;但基本概念保持一致。特别是在OpenCV 3.x和4.x版本中&#xff…

贪吃蛇(使用QT)

贪吃蛇小游戏 一.项目介绍**[贪吃蛇项目地址](https://gitee.com/strandingzy/QT/tree/zyy/snake)**界面一&#xff1a;游戏大厅界面二&#xff1a;关卡选择界面界面三&#xff1a;游戏界面 二.项目实现2.1 游戏大厅2.2关卡选择界面2.3 游戏房间2.3.1 封装贪吃蛇数据结构2.3.2 …

如何通过谷歌外链快速增加网站流量?

利用谷歌外链提升流量的方法非常直接&#xff0c;但实际上&#xff0c;外链影响的是关键词排名&#xff0c;关键词排名提升了&#xff0c;自然就会有流量&#xff0c;所以谷歌外链不是直接能提升网站流量&#xff0c;而是间接的&#xff0c;下面&#xff0c;我会详细介绍几种有…

验收测试:确保软件符合业务需求和合同要求

目录 前言1. 验收测试的概念1.1 用户验收测试&#xff08;UAT&#xff09;1.2 操作验收测试&#xff08;OAT&#xff09; 2. 验收测试的主要作用2.1 确认业务需求的满足2.2 验证合同要求的实现2.3 提升用户信心 3. 验收测试在整个测试中的地位3.1 测试的最后一道关卡3.2 用户与…

niushop逻辑漏洞

niushop逻辑漏洞 安装环境 这里控制不了 抓包在数据包中可以改数据

非线性系统稳定控制器设及案例仿真(s-function函数)

目录 前沿一、案例11. 系统模型 二、案例21. 系统模型2. 稳定性分析3. 仿真(包含代码)1. 仿真效果2. 仿真结果3. 仿真剖析4. 仿真图与代码 前沿 定义一个系统 x ˙ f ( x , u ) \dot{x} f(x,u) x˙f(x,u), 其中 x x x 为状态变量&#xff0c; u u u 为系统输入&#xff0c…

跨平台终端工具Tabby安装配置与远程ssh连接Linux_ubuntu详细教程

文章目录 前言1. Tabby下载安装2. Tabby相关配置3. Tabby简单操作4. ssh连接Linux4.1 ubuntu系统安装ssh4.2 Tabby远程ssh连接ubuntu 5. 安装内网穿透工具5.1 创建公网地址5.2 使用公网地址远程ssh连接 6. 配置固定公网地址 前言 今天和大家分享一下如何在Windows系统使用Tabb…

构建LVS负载均衡群集

构建LVS负载均衡群集 实验环境实验环境 201:客户端 101:调度器 (需要用到2个网卡) 102:web 103:web 104:NFS存储 101:(添加一个网卡) 2 [root@localhost ~]# cd /etc/sysconfig/network-scripts/ 3 4 //查看另一个网卡的名字ens36 5 [root@localhost network-scrip…

人工智能深度学习系列—深度学习中的相似性追求:Triplet Loss 全解析

文章目录 1. 背景介绍2. Loss计算公式3. 使用场景4. 代码样例5. 总结 1. 背景介绍 在机器学习和模式识别领域&#xff0c;相似性度量是核心问题之一。Triplet Loss&#xff0c;作为一种特殊的损失函数&#xff0c;被设计用来学习数据的相对距离&#xff0c;从而使得相似样本更…

5.C_Demo_排序

冒泡排序法 原理&#xff1a; 依次比较相邻的两个元素&#xff0c;如果顺序错误就交换。 思路&#xff1a; 这种方法&#xff0c;显然需要很多轮才能完成&#xff0c;每一轮只能排序一个最大值或最小值(第一层for)&#xff0c;将全部的数据排序完成&#xff0c;需要很多轮(第…

第三期书生大模型实战营之书生大模型全链路开源开放体系

一. Introduction 大模型是发展通用人工智能的重要途经 二. 开源历程以及InternLM2 2024年1月17日 InternLM2 2开源 三. 书生浦语2.0的主要亮点 3.1 超长上下文 3.2 综合性能全面提升 3.3 优秀的对话和创作体验 3.4 工具调用能力整体升级 3.5 突出的数理能力和实用的…