面试之Java的SPI机制详细讲解你会吗?

news2024/10/6 22:18:39

很多小伙伴对SPi不是很熟悉,今天我给大家详细讲解分享下:
Java之SPI机制详细目录
1: SPI机制简介
2: SPI原理
3: 使用场景
4: 源码论证
5: 实战
6: 优缺点
6.1 优点
6.2 缺点
java spi

Java之SPI机制详解
1: SPI机制简介
SPI 全称是 Service Provider Interface,是一种 JDK 内置的动态加载实现扩展点的机制,通过 SPI 技术我们可以动态获取接口的实现类,不用自己来创建。这个不是什么特别的技术,只是 一种设计理念。

2: SPI原理
在这里插入图片描述

Java SPI 实际上是基于接口的编程+策略模式+配置文件组合实现的动态加载机制。

系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。

Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以SPI的核心思想就是解耦。

3: 使用场景
调用者根据实际使用需要 启用、扩展、或者替换框架的实现策略

下面是一些使用了该机制的场景

JDBC驱动,加载不同数据库的驱动类
Spring中大量使用了SPI,比如:对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等
Dubbo中也大量使用SPI的方式实现框架的扩展, 不过它对Java提供的原生SPI做了封装,允许用户扩展实现Filter接口
Tomcat 加载 META-INF/services下找需要加载的类
SpringBoot项目中 使用@SpringBootApplication注解时,会开始自动配置,而启动配置则会去扫描META-INF/spring.factories下的配置类
4: 源码论证
4.1 应用程序调用ServiceLoader.load方法
ServiceLoader.load方法内先创建一个新的ServiceLoader,并实例化该类中的成员变量

private static final String PREFIX = "META-INF/services/";

private ServiceLoader(Class 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();
}

/** 
 * 
 * 在调用该方法之后,迭代器方法的后续调用将延迟地从头开始查找和实例化提供程序,就像新创建的加载程序所做的		  那样
 */

public void reload() {
providers.clear(); //清除此加载程序的提供程序缓存,以便重新加载所有提供程序。
lookupIterator = new LazyIterator(service, loader);
}

private class LazyIterator implements Iterator<S>{

    Class<S> service;
    ClassLoader loader;
    Enumeration<URL> configs = null;
    Iterator<String> pending = null;
    String nextName = 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);
                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;
    }
}

	/** 
 	* 
 	*  通过反射 实例化配置文件中的具体实现类、 另外如果大家在学习Java过程中遇到问题或者迷茫没有方向可以私聊我、本人专注Java开发6年,辅导过百名学弟成功拿到10k+offer!
	 */
	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
    }

5: 实战
步骤1 新建以下类

public interface IService {

/**
 * 获取价格
 * @return
 */
String getPrice();

/**
 * 获取规格信息
 * @return
 */
String getSpecifications();

}
public class GoodServiceImpl implements IService {

@Override
public String getPrice() {
    return "2000.00元";
}

@Override
public String getSpecifications() {
    return "200g/件";
}

}

public class MedicalServiceImpl implements IService {

@Override
public String getPrice() {
    return "3022.12元";
}

@Override
public String getSpecifications() {
    return "30粒/盒";
}

}

步骤2、在 src/main/resources/ 下建立 /META-INF/services 目录, 新增一个以接口命名的文件 org.example.IService.txt 。内容是要应用的实现类,我这边需要放入的数据如下

org.example.GoodServiceImpl
org.example.MedicalServiceImpl
步骤3、使用 ServiceLoader 来加载配置文件中指定的实现。

public class Main {
public static void main(String[] args) {
final ServiceLoader serviceLoader = ServiceLoader.load(IService.class);
serviceLoader.forEach(service -> {
System.out.println(service.getPrice() + “=” + service.getSpecifications());
});
}
}
输出:

2000.00元=200g/件
3022.12元=30粒/盒
6: 优缺点
6.1 优点
解耦 使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起,应用程序可以根据实际业务情况启用框架扩展或替换框架组件。相比使用提供接口jar包,供第三方服务模块实现接口的方式,SPI的方式使得源框架,不必关心接口的实现类的路径

6.2 缺点
虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类
多个并发多线程使用ServiceLoader类的实例是不安全的!
另外如果大家在学习Java过程中遇到问题或者迷茫没有方向可以私聊我、本人专注Java开发6年,辅导过百名学弟成功拿到10k+offer!
**以上就是关于Java的SPI详细讲解欢迎留言交流讨论,本文转载自网络,如有侵权找我删除!

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

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

相关文章

Django整合mysqlclinet步骤

准备工作 要在 Django 中使用 MySQL 数据库&#xff0c;您需要完成以下步骤&#xff1a; 安装 MySQL 服务器和客户端。你可以从官方网站下载并安装&#xff1a;https://www.mysql.com/downloads/ 安装 mysqlclient。mysqlclient 是一个 Python 的第三方库&#xff0c;用于连接…

chapter-7数据库事务

以下课程来源于MOOC学习—原课程请见&#xff1a;数据库原理与应用 考研复习 DBMS保证系统中一切事务的原子性、一致性、隔离性和持续性 DBMS必须对事务故障、系统故障和介质故障进行恢复 恢复中最经常使用的技术&#xff1a;数据库转储和登记日志文件 恢复的基本原理&#…

十二、网络规划与设计

&#xff08;一&#xff09;网络设计基础 1、网络系统生命周期 &#xff08;1&#xff09;四阶段周期&#xff08;重叠&#xff09; 构思与规划阶段、分析与设计阶段、实施与构建阶段、运行与维护阶段 特点&#xff1a;能够快速适应新的需求变化&#xff0c;成本低&#xf…

【SQL 初阶教程】一文轻松玩转 SQL

目录 一、SQL 通用语法 二、SQL 语句的分类 三、DDL语句 DDL——数据库操作 查询所有数据库 语句&#xff1a; SHOW DATABASES;&#xff08;大小写均可&#xff0c;建议大写&#xff09; 创建数据库HSK 语句&#xff1a;CREATE DATABASE HSK; 删除数据库HSK 语句 &#…

关于本地git通过ssh链接github时 time out问题的解决方法

目录问题描述解决方法问题描述 我们如果想要用git ssh链接到远端github&#xff0c;进行repo的clone等操作时&#xff0c;会进行如下的操作&#xff1a; 首先在ssh端命令生成rsa秘钥&#xff0c;命令如下&#xff1a; ssh-keygen -t rsa -C “你的git绑定的邮箱名字”然后在g…

【HBase-读写流程】HBase的读写流程与内部执行机制

【HBase-读写流程】HBase的读写流程与内部执行机制1&#xff09;HBase 读取数据流程1.1.文字描述1.2.流程图2&#xff09;HBase 写入数据流程2.1.文字描述2.2.流程图3&#xff09;flush 机制与 compact 机制的原理3.1.文字描述3.2.流程图1&#xff09;HBase 读取数据流程 1.1.…

游戏开发之Unity2021URP项目场景的构建

地面的修改和编辑&#xff1a;地面插件的使用 打开包管理器&#xff0c;在左边的包那里选择“Unity注册表”&#xff0c;在右边进行搜索“Polybrush”&#xff0c;之后选择右下角的安装 安装完之后要选择样本中的URP进行导入&#xff0c;因为我们的项目是URP渲染管线的&#x…

IronOCR for .NET crack,IronOCR的独特功能

IronOCR for .NET crack,IronOCR的独特功能  在IronTesseract上添加了新的“ReadPdfAndOverlayText”方法&#xff0c;该方法允许您添加文本并保留原始PDF书签/注释。 添加了对存储在应用程序子文件夹中的.config和.json文件中的许可证密钥的支持。 将IronSoftware.System.Dra…

shell 函数和数组作业

1、编写函数&#xff0c;实现打印绿色OK和红色FAILED,判断是否有参数&#xff0c;存在为Ok&#xff0c;不存在为FAILED 2、编写函数&#xff0c;实现判断是否无位置参数&#xff0c;如无参数&#xff0c;提示错误 3、编写函数实现两个数字做为参数&#xff0c;返回最大值 4、…

Minecraft 1.12.2模组开发(五十六) 网络(Networking)

我们本次在模组中实现客户端向服务器发送数据的功能。 演示效果演示效果演示效果 1.新建packet包&#xff0c;包中新建PacketHandler类&#xff1a; PacketHandler.java package com.joy187.mcjoygun.packet;import com.joy187.mcjoygun.Main; import com.joy187.mcjoygun.u…

Huggingface微调BART的代码示例:WMT16数据集训练新的标记进行翻译

BART模型是用来预训练seq-to-seq模型的降噪自动编码器&#xff08;autoencoder&#xff09;。它是一个序列到序列的模型&#xff0c;具有对损坏文本的双向编码器和一个从左到右的自回归解码器&#xff0c;所以它可以完美的执行翻译任务。 如果你想在翻译任务上测试一个新的体系…

Java Stream API 操作完全攻略:让你的代码更加出色 (四)

前言 Java Stream 是一种强大的数据处理工具&#xff0c;可以帮助开发人员快速高效地处理和转换数据流。使用 Stream 操作可以大大简化代码&#xff0c;使其更具可读性和可维护性&#xff0c;从而提高开发效率。本文将为您介绍 Java Stream 操作的所有方面&#xff0c;包括 ran…

交友项目【通用设置】三个功能实现

目录 1&#xff1a;交友项目【通用设置】 1.1&#xff1a;查询通用设置 1.1.1&#xff1a;接口地址 1.1.2&#xff1a;流程分析 1.1.3&#xff1a;代码实现 1.2&#xff1a;设置陌生人问题 1.2.1&#xff1a;接口地址 1.2.2&#xff1a;流程分析 1.2.3&#xff1a;代码…

Python 小型项目大全 51~55

五十一、九十九瓶的变体 原文&#xff1a;http://inventwithpython.com/bigbookpython/project51.html 在歌曲“九十九瓶”的这个版本中&#xff0c;该程序通过删除一个字母、交换一个字母的大小写、调换两个字母或重叠一个字母&#xff0c;在每个小节中引入了一些小的不完美。…

4月,我从外包公司离职了

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了四年的功能测试…

python学习

1.安装 Download Python | Python.org 安装时&#xff0c;点击添加路径。 1.1 python的解释器 我们把代码写进.py结尾的文件里&#xff0c;然后 python 路径文件名就可以运行它了。 2.字面量 例如print("我们"),"我们",就是字符串字面量&#xff0c;…

简化你的代码,提高生产力:这10个Lambda表达式必须掌握

前言 Lambda表达式是一种在现代编程语言中越来越常见的特性&#xff0c;可以简化代码、提高生产力。这篇文章将介绍10个必须掌握的Lambda表达式&#xff0c;这些表达式涵盖了在实际编程中经常用到的常见场景&#xff0c;例如列表操作、函数组合、条件筛选等。通过学习这些Lambd…

JUC源码系列-CountDownLatch源码研读

前言 CountDownLatch是一个很有用的工具&#xff0c;latch是门闩的意思&#xff0c;该工具是为了解决某些操作只能在一组操作全部执行完成后才能执行的情景。例如&#xff0c;小组早上开会&#xff0c;只有等所有人到齐了才能开&#xff1b;再如&#xff0c;游乐园里的过山车&…

运行时内存数据区之堆(二)

Minor GC、Major GC、与Full GC JVM在进行GC时&#xff0c;并非每次都对上面三个内存&#xff08;新生代、老年代&#xff1a;方法区&#xff09;区域一起回收的&#xff0c;大部分时候回收的都是指新生代。 针对HotSpot VM的实现&#xff0c;它里面的GC按照回收区域又分为两…

浅谈 如果做微服务了 这个模块怎么去划分?

如果做微服务了 这个模块怎么去划分&#xff1f; 还是高内聚 低耦合的一个思想吧 &#xff0c;单一职责的设计原则&#xff0c;也是一个封装的思想吧&#xff0c; 业务维度&#xff1a; ​ 按照业务的关联程度来决定&#xff0c;关联比较密切的业务适合拆分为一个微服务&…