SPI机制
一、SPI简介
SPI(Service Provider Interface)机制是一种服务发现机制,广泛用于Java生态中。它允许框架或库通过接口解耦具体实现,用户可以在运行时动态地提供接口的实现,而不是在编译时确定。这种机制在很多场景下非常有用,比如数据库驱动、日志框架、解析库等。
二、SPI原理
SPI机制的核心思想是,接口的实现类通过配置文件进行声明,而框架或服务的调用方在运行时可以动态加载这些实现类。
1. 接口定义
首先,开发者定义一个接口(或抽象类),该接口规定了需要实现的功能。例如,一个简单的接口可以是这样:
public interface MyService {void execute();
}
2. 服务提供者(实现接口的类)
接下来,不同的提供者可以实现这个接口,提供不同的功能。例如,提供者A和提供者B分别实现MyService接口:
public class MyServiceProviderA implements MyService {@Overridepublic void execute() {System.out.println("MyServiceProviderA execution");}
}public class MyServiceProviderB implements MyService {@Overridepublic void execute() {System.out.println("MyServiceProviderB execution");}
}
3. 定义服务提供者配置文件
SPI机制依赖于META-INF/services目录下的配置文件。配置文件的名称必须是接口的全限定名,内容是接口实现类的全限定名。对于MyService接口,其配置文件应该命名为:
META-INF/services/com.example.MyService
该文件内容如下,包含每个实现类的全限定类名:
com.example.MyServiceProviderA
com.example.MyServiceProviderB
4. 使用ServiceLoader动态加载服务
Java提供了ServiceLoader类来查找和加载服务。ServiceLoader会扫描META-INF/services目录,并加载该接口的所有实现类:
ServiceLoader<MyService> serviceLoader = ServiceLoader.load(MyService.class);
for (MyService service : serviceLoader) {service.execute();
}
ServiceLoader会自动查找META-INF/services/com.example.MyService文件,并实例化该文件中定义的所有类。每个实现类的execute方法都会被调用。
三、SPI的优劣
SPI的优势
- 解耦性:通过接口来解耦,实现类可以动态加载,调用方不需要依赖具体实现。
- 扩展性强:可以很方便地通过提供不同的实现类来扩展功能,不需要修改框架代码。
- 动态加载:SPI机制允许在运行时动态选择或加载实现类,这非常适合插件化的框架。
SPI的局限性
尽管SPI提供了很大的灵活性,但也有一些缺点:
- 无法选择实现:
ServiceLoader会加载所有提供者,如果你有多个实现类,你无法直接选择使用哪一个,除非通过额外的逻辑来筛选。 - 性能问题:每次调用
ServiceLoader.load都会遍历META-INF/services目录并加载类,这对性能可能有一定的影响。 - 安全性问题:加载外部服务实现时,如果实现类存在安全漏洞或者不安全的代码,可能导致问题。
SPI的典型应用场景
- JDBC:JDBC是SPI的典型应用。JDBC接口定义了数据库操作的规范,不同的数据库驱动程序(如MySQL、Oracle等)通过实现JDBC接口为数据库提供访问功能。通过
META-INF/services文件,JDBC驱动在运行时可以被动态加载,无需硬编码到应用程序中。 - 日志框架:如SLF4J和Log4J,它们都提供了统一的接口,实际的日志实现可以是多种实现之一,比如Logback、Log4J等。框架可以在运行时根据配置文件加载不同的日志实现。
四、自定义SPI示例
1. 接口定义
public interface PaymentService {void pay(int amount);
}
2. 实现类
public class AlipayService implements PaymentService {@Overridepublic void pay(int amount) {System.out.println("Paid " + amount + " using Alipay");}
}public class WeChatPayService implements PaymentService {@Overridepublic void pay(int amount) {System.out.println("Paid " + amount + " using WeChat Pay");}
}
3. 配置文件
在META-INF/services目录下创建文件com.example.PaymentService,内容为:
com.example.AlipayService
com.example.WeChatPayService
4. 使用ServiceLoader加载并调用实现
ServiceLoader<PaymentService> services = ServiceLoader.load(PaymentService.class);
for (PaymentService service : services) {service.pay(100); // 调用所有服务提供者的pay方法
}
输出结果:
Paid 100 using Alipay
Paid 100 using WeChat Pay
总结
SPI机制为Java生态中的扩展和插件提供了标准的方式,尤其是在跨多个库或框架中非常有用。通过ServiceLoader和META-INF/services的配置,Java程序可以在运行时动态加载实现类,从而实现高扩展性和解耦。
五、Spring的伪SPI机制
Spring中的“伪SPI”机制,实际上是一种通过依赖注入和工厂模式实现的动态服务加载机制,和Java原生的SPI机制有一定的相似之处,但Spring通过容器管理的方式提供了更灵活、更强大的功能。
与Java原生的SPI机制相比,Spring的伪SPI机制不依赖ServiceLoader,而是通过Spring IoC容器的动态管理和加载实现类。这种机制不仅支持动态扩展,而且更具灵活性,因为它可以配合Spring的其他特性(如AOP、条件注解等)进行控制和优化。
Spring中的伪SPI机制实现方式
Spring提供了几种不同的方式实现类似SPI的动态加载机制,以下是一些常见的实现方式:
1. 通过@Conditional注解实现条件加载
Spring允许你根据不同的条件来动态加载Bean。这类似于SPI的选择性加载机制。
- 示例:
假设你有两个支付服务实现(类似前面的例子),你希望根据某些条件(如配置文件中的设置)来选择哪个服务被加载。
public interface PaymentService {void pay(int amount);
}public class AlipayService implements PaymentService {@Overridepublic void pay(int amount) {System.out.println("Paid " + amount + " using Alipay");}
}public class WeChatPayService implements PaymentService {@Overridepublic void pay(int amount) {System.out.println("Paid " + amount + " using WeChat Pay");}
}
你可以通过@Conditional注解动态选择加载哪一个支付服务实现:
- 基于配置文件的条件加载:
@Configuration
public class PaymentServiceConfig {@Bean@ConditionalOnProperty(name = "payment.service", havingValue = "alipay")public PaymentService alipayService() {return new AlipayService();}@Bean@ConditionalOnProperty(name = "payment.service", havingValue = "wechatpay")public PaymentService weChatPayService() {return new WeChatPayService();}
}
然后在application.properties中设置:
payment.service=alipay
这样,Spring容器在启动时会根据配置文件中的payment.service属性来决定加载哪个支付服务。
2. 使用@Primary或@Qualifier注解选择具体实现
当你有多个实现类时,Spring提供了选择加载哪个实现类的能力。使用@Primary可以设置默认的实现,而使用@Qualifier可以进行精确选择。
- 示例:
@Configuration
public class PaymentServiceConfig {@Bean@Primarypublic PaymentService alipayService() {return new AlipayService();}@Beanpublic PaymentService weChatPayService() {return new WeChatPayService();}
}
在使用时,你可以通过@Qualifier来精确指定使用哪一个实现:
@Service
public class PaymentProcessor {private final PaymentService paymentService;@Autowiredpublic PaymentProcessor(@Qualifier("weChatPayService") PaymentService paymentService) {this.paymentService = paymentService;}public void processPayment(int amount) {paymentService.pay(amount);}
}
这样,PaymentProcessor会使用weChatPayService作为具体的支付实现,而不是默认的alipayService。
3. 使用spring.factories文件的动态扩展
在Spring Boot中,类似于Java原生SPI的机制通过spring.factories文件来实现。这种方式通常用于自动配置类的加载,Spring Boot的自动配置依赖于它。
- 示例:
在META-INF/spring.factories文件中,可以定义哪些类会在应用启动时自动加载:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.config.AlipayAutoConfiguration,\
com.example.config.WeChatPayAutoConfiguration
每当Spring Boot启动时,它会自动读取spring.factories文件并加载配置的类。这种方式允许开发者在不修改代码的情况下动态增加或替换实现,达到类似SPI的动态扩展效果。
4. 使用@AutoConfiguration机制
Spring Boot引入了自动配置机制,通过@AutoConfiguration和条件注解组合使用,使得自动装配更加灵活。可以通过依赖项的存在与否、配置属性等多种条件,动态决定是否装配某个实现。
- 示例:
@Configuration
@ConditionalOnClass(name = "com.example.payment.AlipayService")
public class AlipayAutoConfiguration {@Beanpublic PaymentService alipayService() {return new AlipayService();}
}
在这个例子中,只有在类路径上存在AlipayService类时,才会自动配置并创建alipayService的Bean。
5. 使用FactoryBean动态加载Bean
FactoryBean是Spring提供的一种特殊的Bean,它可以控制Bean的创建过程,允许根据逻辑条件返回不同的实现类。这为动态加载不同的服务提供了更多的灵活性。
- 示例:
public class PaymentServiceFactoryBean implements FactoryBean<PaymentService> {private String paymentType;public PaymentServiceFactoryBean(String paymentType) {this.paymentType = paymentType;}@Overridepublic PaymentService getObject() throws Exception {if ("alipay".equals(paymentType)) {return new AlipayService();} else {return new WeChatPayService();}}@Overridepublic Class<?> getObjectType() {return PaymentService.class;}@Overridepublic boolean isSingleton() {return true; // 返回true表示该Bean是单例的}
}
然后在配置类中使用:
@Configuration
public class PaymentConfig {@Beanpublic FactoryBean<PaymentService> paymentServiceFactoryBean() {return new PaymentServiceFactoryBean("alipay");}
}
这样,通过FactoryBean,你可以在运行时动态决定返回哪一个实现类。
六、Spring伪SPI机制与原生SPI的比较
| 特性 | 原生SPI | Spring伪SPI |
| 加载方式 |
|
, , |
| 配置文件 |
|
|
| 选择实现方式 | 自动加载所有实现,无法选择特定实现 | 通过 或 等灵活选择 |
| 扩展性 | 不支持条件加载,无法与其他特性集成 | 支持各种条件加载,灵活扩展 |
| 依赖注入支持 | 无依赖注入 | 支持依赖注入,且与Spring框架无缝集成 |
| 控制复杂性 | 配置简单,但功能相对单一 | 功能强大,可以结合多种条件、注解及工厂模式 |
总结
Spring的“伪SPI”机制通过依赖注入、条件注解、自动配置等机制,实现了比Java原生SPI更加灵活、强大的服务加载和扩展功能。它不仅能像SPI一样动态加载服务,还能根据各种条件灵活地控制加载行为,更适合复杂应用场景下的模块化和插件化开发。
这种伪SPI机制广泛应用于Spring Boot自动配置、扩展点设计和插件化架构中,极大地提升了Spring应用的扩展性和灵活性。
相关文章:
SPI机制
一、SPI简介 SPI(Service Provider Interface)机制是一种服务发现机制,广泛用于Java生态中。它允许框架或库通过接口解耦具体实现,用户可以在运行时动态地提供接口的实现,而不是在编译时确定。这种机制在很多场景下非…...
生信分析流程:从数据准备到结果解释的完整指南
介绍 生物信息学(生信)分析是一个复杂的过程,涉及从数据准备到结果解释的多个步骤。随着高通量测序技术的发展和生物数据的迅猛增长,了解和掌握生信分析的标准流程变得尤为重要。这不仅有助于提高分析的准确性,还能优…...
golang语法
参考链接:https://www.runoob.com/go/ 创建变量 // 3种方法 var a int a : 10 // 类型推断 a : make() // 复合类型循环 // 3种循环 for i : 0; i < 10; i {// 循环体} // 传统for循环 for index, num : range nums {// 循环体} // nums是可迭代的复合类型…...
【fisco学习记录2】多群组搭建
说明 文档参考: 多群组部署 — FISCO BCOS 2.0 v2.11.0 文档 (fisco-bcos-documentation.readthedocs.io) 多群组搭建之前,先暂停之前的单群组,并删除: cd fisco bash nodes/127.0.0.1/stop_all.sh rm -rf nodes/ 实现图&…...
深度解读:路由交换、负载均衡与防火墙的网络交响
一、路由交换:网络流动的“大动脉” 1. 路由:决定命运的“路径规划师” 路由技术如同现代交通网络中的导航系统,决定了数据从起点到终点的最佳路径。路由器基于网络层IP地址,对每个数据包进行精确的路径选择,并确保其…...
linux线程 | 线程的控制(二)
前言: 本节内容是线程的控制部分的第二个小节。 主要是列出我们的线程控制部分的几个细节性问题以及我们的线程分离。这些都是需要大量的代码去进行实验的。所以, 准备好接受新知识的友友们请耐心观看。 现在开始我们的学习吧。 ps:本节内容适合了解线程…...
npm install报错一堆sass gyp ERR!
执行npm install ,出现一堆gyp含有sass错误的情况下。 解决办法: 首页可能是node版本问题,太高或者太低,也会导致npm install安装错误(不会自动生成node_modules文件),本次试验,刚开…...
微知-BlueField DPU在lspci中显示Flash Recovery是什么意思?
效果: lspci |grep BlueField10:00.0 Memory controller: Mellanox Technologies MT42822 Family [BlueField-2 SoC Flash Recovery] (rev 01)*原因: 表示此时flash是empty空的,或者在flash中的FW是无法工作的。比如烧录错误。 这里指的一提…...
【前端知识点】前端笔记
css 引入css文件的文件路径 <!-- 引入外部 CSS 文件 --> <!-- 当前文件所在文件夹目录 --> <link rel"stylesheet" href"./"> <!-- 当前文件所在父文件夹目录 --> <link rel"stylesheet" href"../">j…...
Sping Cache 使用详解
缓存是提升应用性能的常用手段。它通过将耗时的操作结果存储起来,下次请求可以直接从缓存中获取,从而避免重复计算或查询数据库,显著减少响应时间和服务器负载。Spring 框架提供了强大的缓存抽象 Spring Cache,它简化了缓存的使用…...
动手学深度学习60 机器翻译与数据集
1. 机器翻译与数据集 import os import torch from d2l import torch as d2l#save d2l.DATA_HUB[fra-eng] (d2l.DATA_URL fra-eng.zip,94646ad1522d915e7b0f9296181140edcf86a4f5)#save def read_data_nmt():"""载入“英语-法语”数据集"&qu…...
Python网络爬虫技术
Python网络爬虫技术详解 引言 网络爬虫(Web Crawler),又称网络蜘蛛(Web Spider)或网络机器人(Web Robot),是一种按照一定规则自动抓取互联网信息的程序或脚本。它们通过遍历网页链…...
黑马程序员-redis项目实践笔记1
目录 一、 基于Session实现登录 发送验证码 验证用户输入验证码 校验登录状态 Redis代替Session登录 发送验证码修改 验证用户输入验证码 登录拦截器的优化 二、 商铺查询缓存 缓存更新策略 数据库和缓存不一致解决方案 缓存更新策略的最佳实践方案 实现商铺缓…...
ES-入门聚合查询
url 请求地址 http://192.168.1.108:9200/shopping/_search {"aggs": { //聚合操作"price_group":{ //名称,随意起名"terms":{ //分组"field": "price" //分组字段}}} } 查询出来的结果是 查询结果中价格的平均值 {&q…...
七维大脑: 探索人类认知的未来之路
七维大脑: 探索人类认知的未来之路 随着科技的不断发展,人们对于大脑的认知也在不断扩展。近年来,科学家们提出了一个名为“七维大脑”的概念,试图通过七个维度来理解人类的认知过程。这个概念的提出,让人们开始思考&…...
spring |Spring Security安全框架 —— 认证流程实现
文章目录 开头简介环境搭建入门使用1、认证1、实体类2、Controller层3、Service层3.1、接口3.2、实现类3.3、实现类:UserDetailsServiceImpl 4、Mapper层3、自定义token认证filter 注意事项小结 开头 Spring Security 官方网址:Spring Security官网 开…...
Django+vue自动化测试平台---正式开源!!!
自动化测试:接口、Web UI 与 App 的全面探索 在此郑重声明:本文内容未经本人同意,不得随意转载。若有违者,必将追究其法律责任。同时,禁止对相关源码进行任何形式的售卖行为,本内容仅供学习使用。 Git 地…...
电子电气架构 --- 智能网联汽车未来是什么样子?
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…...
docker安装elasticsearch(es)+kibana
目录 docker安装elasticsearch 一.准备工作 1.打开docker目录 2.创建elasticsearch目录 3.打开elasticsearch目录 4.拉取elasticsearch镜像 5.检查镜像 二.挂载目录 1.创建数据挂载目录 2.创建配置挂载目录 3.创建插件挂载目录 4.权限授权 三.编辑配置 1.打开con…...
大厂面试真题-说说redis的雪崩、击穿和穿透
缓存雪崩、击穿、穿透是缓存系统中常见的三种问题,它们都会对系统的性能和稳定性造成严重影响。以下是对这三种问题的详细解释以及相应的解决方案: 一、缓存雪崩 问题解释: 缓存雪崩指的是因为某些原因导致缓存中大量的数据同时失效或过期…...
如何突破Windows权限壁垒?系统管理专家的秘密武器
如何突破Windows权限壁垒?系统管理专家的秘密武器 【免费下载链接】NSudo [Deprecated, work in progress alternative: https://github.com/M2Team/NanaRun] Series of System Administration Tools 项目地址: https://gitcode.com/gh_mirrors/ns/NSudo 在W…...
LabVIEW新手避坑指南:用For循环和数组搞定水仙花数,别再手动算啦!
LabVIEW实战:用For循环与数组高效求解水仙花数的5个关键技巧 水仙花数这个经典的编程练习题,在文本编程语言中可能只需十几行代码,但切换到LabVIEW的图形化编程环境时,不少初学者会陷入连线混乱和逻辑纠结。本文将从实际工程视角…...
开源像素艺术大模型教程:Pixel Dream Workshop Windows/Mac双平台部署
开源像素艺术大模型教程:Pixel Dream Workshop Windows/Mac双平台部署 1. 像素幻梦创意工坊简介 Pixel Dream Workshop(像素幻梦创意工坊)是一款基于FLUX.1-dev扩散模型的像素艺术生成工具。它采用独特的16-bit像素风格界面设计,…...
KMS_VL_ALL_AIO激活工具完全指南:从问题诊断到长效管理
KMS_VL_ALL_AIO激活工具完全指南:从问题诊断到长效管理 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO 如何诊断Windows/Office激活失败的核心原因? 1.1 激活失败的三大…...
Step3-VL-10B部署案例:金融APP界面自动化测试,覆盖85%人工回归用例
Step3-VL-10B部署案例:金融APP界面自动化测试,覆盖85%人工回归用例 1. 项目背景与痛点 金融APP的每一次版本更新,都伴随着一场紧张的回归测试。测试团队需要反复验证登录、转账、理财购买、账单查询等几十个核心功能,确保新代码…...
【紧急预警】Mojo nightly build已悄然移除PyModule::import() API!立即备份旧版+迁移至PyO3 0.21+手动GC管理方案(附自动化迁移脚本)
第一章:【紧急预警】Mojo nightly build已悄然移除PyModule::import() API!立即备份旧版迁移至PyO3 0.21手动GC管理方案(附自动化迁移脚本)Mojo nightly build v2024.06.12 起,PyModule::import() 已被彻底移除&#x…...
别再乱选了!Ansys EDA桌面版导入IBIS模型,Pin Import和Buffer Import到底怎么用?
Ansys EDA桌面版IBIS模型导入指南:Pin Import与Buffer Import深度解析 在信号完整性(SI)和电源完整性(PI)仿真领域,IBIS模型的使用一直是工程师们关注的焦点。作为行业标准的Ansys EDA工具链(原E-desktop)提供了强大的SIPI仿真能…...
手把手教你用魔塔社区+LLaMA-Factory,免费微调Qwen2.5-7B模型(保姆级避坑指南)
零成本玩转Qwen2.5-7B微调:魔塔社区LLaMA-Factory实战手册 最近在开源模型社区里,Qwen2.5系列凭借其优秀的对话能力和中文理解表现,迅速成为开发者们的新宠。但很多朋友反馈,虽然想尝试微调这个模型来适配自己的业务场景ÿ…...
掌握 AgentScope 与 Spring AI Alibaba:大模型多智能体实践指南(收藏版)
本文深入探讨了 AgentScope 与 Spring AI Alibaba 在大模型应用中的多智能体实践。从单智能体优先原则出发,详细解析了 Pipeline、Routing、Skills、Subagents、Supervisor、Handoffs 及 Custom Workflow 等多种多智能体模式,并提供了实用的架构选型指南…...
英雄联盟智能助手:如何用League Toolkit提升你的游戏体验
英雄联盟智能助手:如何用League Toolkit提升你的游戏体验 【免费下载链接】League-Toolkit 兴趣使然的、简单易用的英雄联盟工具集。支持战绩查询、自动秒选等功能。基于 LCU API。 项目地址: https://gitcode.com/gh_mirrors/le/League-Toolkit 在英雄联盟的…...
