SPI 这么玩就很优雅!
八股文背多了相信大家都听说过一个词SPI 扩展。有的面试官就很喜欢问这个问题SpringBoot 的自动装配是如何实现的基本上你一说是基于 spring 的 SPI 扩展机制再把spring.factories文件和EnableAutoConfiguration提一下那么这个问题就答的八九不离十了。就像四五年前我去面试的时候被问到这个问题SPI 动态扩展机制这几个词从嘴里一说出来就把面试官唬的一愣一愣的。可能他们也没见过这么能装逼的一句话能简简单单说明白非要拽一个听上去很高大上的词。话说回来被唬住的可不止是面试官其实还有我自己。至于 SPI 扩展究竟是个啥是怎么实现的我当时也根本不明白。不过现在的面试就是这样对线八股文要想唬住面试官就得先唬住自己。那么我们今天暂且不提 spring 的 SPI 扩展先来看看 java 本身自带的 SPI 扩展机制是怎么一回事。1、简介SPI 的全称是Service Provider Interface翻译过来就是服务提供者的接口它所实现的其实是一种服务的发现机制。这么说起来可能还是有点不好理解我举个例子来类比一下。在 spring 项目中写 service 层代码前会约定俗成的会添加一个接口层。然后通过 spring 中的依赖注入可以借助Autowired等方式注入这个接口的实现类的实例对象之后对于 service 的调用一般也基于接口操作。简单形容就是这样的如图所示接口、实现类都是由服务提供方提供我们可以把 controller 看作服务调用者调用方只管调用接口就可以了。虽然也有声音认为大部分情况下 service 只有一个实现类接口层显得有些多余。但是在《Head First Design Patterns》这本书中大佬们还是建议过Program to an interface, not an implementation.没错就是常说的要面向接口编程。至于好处也不外乎是降低耦合度、方便日后扩展、提高了代码的灵活性和可维护性等等。在上面这个例子里这个接口层和其中的方法我们可以称之为API而我们要讨论的SPI和它相比有类似也有差异还是先看图简单来说就是服务的调用方定义一个接口规范可以由不同的服务提供者实现。并且调用方能够通过某种机制来发现服务提供方并通过接口调用它的能力。通过对比我们可以看出它们虽然都有着接口这一层面但还是有很大的不同API 中的接口是服务提供者给服务调用者的一个功能列表而 SPI 中更多强调的是服务调用者对服务实现的一种约束服务提供者根据这种约束实现的服务可以被服务调用者发现。说白了Java 中的 SPI 实现的就是你按我的接口规范实现服务我就能通过某种机制为这个接口寻找到这个服务。这么说起来可能还有些抽象下面我们举一个例子类比具体描述一下这个过程。2、定义接口说起智能家居系统大家现在都比较熟悉了只要是相同品牌下的产品连上 wifi 就能够通过手机 app 控制了非常方便。虽然产品不断更新换代型号更新层出不穷但是同种家电在 app 上操作起来功能一般都是一样的。就拿空调来说我们在 app 上操作起来一般也就三个主要功能开关选模式调节温度。假设我现在在客厅、卧室、书房安装了 3 款不同型号的空调并把它们都接入到了我 app 中那么之后的操作都是相同的几个按键简单粗暴。思考一下无论是开关还是调温都是通过 app 去调用设备的接口罢了那么如果不同型号的空调各写各的接口后端 app 在开发的时候光对接接口都麻烦的要死。解决方法也很简单我先定义一套接口规范不管你以后什么型号的空调都按我的规范来实现接口。以后只要我能发现你的设备那么都可以按相同的方法来调用接口。那么下面就先来定义这么一套接口规范如果你以后想要接入智能家居系统那么就要遵循这个规范来开发接口。新建一个项目作为标准就叫aircondition-standard好了然后创建一个接口。除了 3 个操作以外我们再添加一个获取空调型号的方法。public interface IAircondition {// 获取型号String getType();// 开关void turnOnOff();// 调节温度void adjustTemperature(int temperature);// 模式变更void changeModel(int modelId);}这个接口后面要给服务的实现方来使用用 maven 把它打成 jar 包mvn clean install之后服务提供者在项目中就可以引入这个 jar 包了有了这套规范就保证了产品后期不管怎么更新换代都能接入到系统来。3、服务实现制定并发布完规则后挂式空调作为第一个服务提供者就来了新建一个项目aircondition-hanging-type并引入刚才打好的 jar 包dependencygroupIdcom.cn.hydra/groupIdartifactIdaircondition-standard/artifactIdversion1.0-SNAPSHOT/version/dependency创建服务类并实现前面定义的接口public class HangingTypeAirconditionimplements IAircondition{public String getType() {return HangingType;}public void turnOnOff() {System.out.println(挂式空调开关);}public void adjustTemperature(int i) {System.out.println(挂式空调调节温度);}public void changeModel(int i) {System.out.println(挂式空调更换模式);}}在项目的resources的目录下创建META-INF/services目录然后以前面定义的接口名com.cn.hydra.IAircondition创建文件并在文件中写入实现类的全限定名。com.cn.hydra.HangingTypeAircondition整个项目结构非常简单这样一个服务方的简单实现就搞定了用 maven 打成 jar 包之后就可以提供给调用方使用了。同理我们可以再创建一个立式空调的项目aircondition-vertical-type也只创建一个服务类public class VerticalTypeAirconditionimplements IAircondition{public String getType() {return VerticalType;}public void turnOnOff() {System.out.println(立式空调开关);}public void adjustTemperature(int i) {System.out.println(立式空调调节温度);}public void changeModel(int i) {System.out.println(立式空调更换模式);}}还是按上面的命名规则创建一个配置文件com.cn.hydra.VerticalTypeAircondition同样打成 jar 包就完事了至于服务调用者如何去发现和调用这两个服务下面详细再说。4、服务发现现在两个服务提供方都实现了接口下面关键的一步就是服务发现这一步 java 中的 spi 发现机制已经帮我们实现好了。创建一个新项目aircondition-app引入上面打好的两个 jar 包。dependenciesdependencygroupIdcom.cn.hydra/groupIdartifactIdaircondition-hanging-type/artifactIdversion1.0-SNAPSHOT/version/dependencydependencygroupIdcom.cn.hydra/groupIdartifactIdaircondition-vertical-type/artifactIdversion1.0-SNAPSHOT/version/dependency/dependencies按照上面的说法虽然每个服务提供者对于接口都有不同的实现但是作为调用者来说它并不需要关心具体的实现类我们要做的是通过接口来调用服务提供者实现的方法。下面就是关键的服务发现环节我们写一个方法根据型号去调用对应空调的开关方法。public class AirconditionApp {public static void main(String[] args) {new AirconditionApp().turnOn(VerticalType);}public void turnOn(String type){ServiceLoaderIAircondition load ServiceLoader.load(IAircondition.class);for (IAircondition iAircondition : load) {System.out.println(检测到:iAircondition.getClass().getSimpleName());if (type.equals(iAircondition.getType())){iAircondition.turnOnOff();}}}}测试结果可以看到测试过程中通过定义的接口IAircondition发现了两个实现类并通过参数调用了特定实现类的某个方法。整段代码中没有出现过具体的服务实现类操作都是通过接口调用。5、原理了解了 spi 的工作流程我们再来看看它的实现其实最关键的就是上面代码中出现的ServiceLoader这个类。上面的示例代码中对于ServiceLoader的load()方法的结果我们用for循环进行了遍历这一点我们看一下源码就能明白因为ServiceLoader实现了Iterable这一接口而整个服务发现的核心就在它的iterator()方法中。注意这里面有两个关键的东西找一下在源码中定义的地方注释写的非常明白providers就是一个缓存在迭代器中如果先从这里面进行查找如果里面有就继续往下找没有了的话就用这个懒加载的lookupIterator查找。那么就简单了接着往下看LazyIterator看看它里面的hasNext()和next()两个方法是怎么实现的。这个acc是一个安全管理器在前面通过System.getSecurityManager()判断并赋值debug 看一下这里都是null所以直接看hasNextService()和nextService()方法就可以了。在hasNextService()方法中会取出接口取出实现类的类名放到nextName中接下来在nextService()方法中则会先加载这个实现类然后实例化对象最终放入缓存中去。在迭代器的迭代过程中会完成所有实现类的实例化其实归根结底还是基于 java 反射去实现的。6、应用要说 spi 的实际应用大家最常见的应该就是日志框架slf4j了它利用 spi 实现了插槽式接入其他具体的日志框架。说白了slf4j本身就是个日志门面并不提供具体的实现需要绑定其他具体实现才能真正的引入日志功能。例如我们可使用log4j2作为具体的绑定器只需要在 pom 中引入slf4j-log4j12就可以使用具体功能。dependencygroupIdorg.slf4j/groupIdartifactIdslf4j-api/artifactIdversion2.0.3/version/dependencydependencygroupIdorg.slf4j/groupIdartifactIdslf4j-log4j12/artifactIdversion2.0.3/version/dependency引入项目后点开它的 jar 包看一下具体结构有没有发现一个彩蛋先说为什么我们 pom 中引入的明明是slf4j-log4j12实际上引入的是slf4j-reload4j翻一下官网的文档大意就是在 2015 年和 2022 年log4j1.x就已经宣布end of life终止了原因也不难猜估计是因为频繁爆出的漏洞。在那之后slf4j-log4j在构建阶段就会自动重定向到slf4j-reload4j了并且官方也强烈建议使用slf4j-reload4j作为替代。再回头看一下 jar 包的META-INF.services里面通过 spi 注入了Reload4jServiceProvider这个实现类它实现了SLF4JServiceProvider这一接口在它的初始化方法initialize()中会完成初始化等工作后续可以继续获取到LoggerFactory和Logger等具体日志对象。7、总结Java 中的 SPI 提供了一种比较特别的服务发现和调用机制通过接口灵活的将服务调用与服务提供者分离用于提供给第三方实现扩展时还是很方便的。但是也有缺点比方说一旦加载一个接口就会把所有实现类都加载进来可能会加载到不需要的冗余服务。不过站在整体角度上还是给我们提供了一种非常不错的框架扩展、集成的思路。

相关新闻

40 张图,精通 Java AQS

40 张图,精通 Java AQS

Java中 AQS 是 AbstractQueuedSynchronizer 类,AQS 依赖 FIFO 队列来提供一个框架,这个框架用于实现锁以及锁相关的同步器,比如信号量、事件等。 在 AQS 中,主要有两部分功能,一部分是操作 state 变量,第二…

2026/7/4 14:37:38 阅读更多 →
技术面试官视角:除了代码,更看重候选人的哪些潜质与特质?

技术面试官视角:除了代码,更看重候选人的哪些潜质与特质?

技术面试官视角:除了代码,更看重候选人的哪些潜质与特质? 在技术面试中,优秀的代码能力是基础,但面试官往往更关注候选人的底层特质。以下是关键考察维度: 一、问题分析与解决能力 需求拆解能力 能否将模…

2026/7/5 22:58:39 阅读更多 →
数字图像处理篇---高斯模糊

数字图像处理篇---高斯模糊

核心比喻:单反相机拍出的“唯美背景”回忆一下你看过的专业人像照片或电影——人物清晰锐利,但背后的风景、灯光,却化成了一片柔和、朦胧、带有光斑的色块。这就是镜头模糊的效果。如果说高斯模糊是“均匀的毛玻璃”,那镜头模糊就…

2026/7/5 11:12:27 阅读更多 →

最新新闻

ReVanced Integrations 中的 SponsorBlock 集成:自动跳过赞助片段的实现指南 [特殊字符]

ReVanced Integrations 中的 SponsorBlock 集成:自动跳过赞助片段的实现指南 [特殊字符]

ReVanced Integrations 中的 SponsorBlock 集成:自动跳过赞助片段的实现指南 🚀 【免费下载链接】revanced-integrations 🔩 ReVanced Integrations containing classes to be merged by ReVanced Patcher 项目地址: https://gitcode.com/g…

2026/7/6 19:39:04 阅读更多 →
HEIF Utility:Windows用户必备的HEIC图片转换终极指南

HEIF Utility:Windows用户必备的HEIC图片转换终极指南

HEIF Utility:Windows用户必备的HEIC图片转换终极指南 【免费下载链接】HEIF-Utility HEIF Utility - View/Convert Apple HEIF images on Windows. 项目地址: https://gitcode.com/gh_mirrors/he/HEIF-Utility 你是否在Windows电脑上遇到过iPhone照片无法打…

2026/7/6 19:39:04 阅读更多 →
JS Analyzer常见问题解决:Jython环境配置与Burp Suite兼容性问题排查终极指南 [特殊字符]

JS Analyzer常见问题解决:Jython环境配置与Burp Suite兼容性问题排查终极指南 [特殊字符]

JS Analyzer常见问题解决:Jython环境配置与Burp Suite兼容性问题排查终极指南 🚀 【免费下载链接】JSAnalyzer 项目地址: https://gitcode.com/gh_mirrors/js/JSAnalyzer JS Analyzer是一款强大的Burp Suite扩展,专门用于JavaScript静…

2026/7/6 19:39:04 阅读更多 →
Instatic插件权限审计:安全合规检查完全指南

Instatic插件权限审计:安全合规检查完全指南

Instatic插件权限审计:安全合规检查完全指南 【免费下载链接】Instatic Instatic is a modern self-hosted visual CMS - get it running in 1 minute 项目地址: https://gitcode.com/GitHub_Trending/in/Instatic Instatic作为现代化的自托管可视化CMS&…

2026/7/6 19:35:01 阅读更多 →
nest_asyncio 性能优化指南:如何避免嵌套事件循环的性能陷阱

nest_asyncio 性能优化指南:如何避免嵌套事件循环的性能陷阱

nest_asyncio 性能优化指南:如何避免嵌套事件循环的性能陷阱 【免费下载链接】nest_asyncio Patch asyncio to allow nested event loops 项目地址: https://gitcode.com/gh_mirrors/ne/nest_asyncio 在Python异步编程的世界中,nest_asyncio是一个…

2026/7/6 19:29:00 阅读更多 →
Hermes Agent完整实战指南:AI代理系统的深度配置与应用

Hermes Agent完整实战指南:AI代理系统的深度配置与应用

Hermes Agent完整实战指南:AI代理系统的深度配置与应用 【免费下载链接】hermes-agent The agent that grows with you 项目地址: https://gitcode.com/GitHub_Trending/he/hermes-agent Hermes Agent是一款功能强大的AI代理系统,它能够随着用户需…

2026/7/6 19:26:59 阅读更多 →

日新闻

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2 与 MySQL 单元测试兼容性:5 个关键 SQL 语句差异与规避方案

H2与MySQL单元测试兼容性:5个关键SQL语句差异与规避方案1. 单元测试中的数据库兼容性挑战在Java开发领域,单元测试是保证代码质量的重要环节。当应用涉及数据库操作时,测试环境的搭建往往成为开发者的痛点。H2数据库因其轻量级、内存模式和快…

2026/7/6 0:01:17 阅读更多 →
Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘

Windows任务栏终极清理指南:用RBTray一键隐藏窗口到系统托盘 【免费下载链接】rbtray A fork of RBTray from http://sourceforge.net/p/rbtray/code/. 项目地址: https://gitcode.com/gh_mirrors/rb/rbtray 你是否厌倦了Windows任务栏上密密麻麻的图标&…

2026/7/6 0:01:17 阅读更多 →
Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C++ 运行时库一键安装终极指南:告别DLL缺失烦恼

Visual C 运行时库一键安装终极指南:告别DLL缺失烦恼 【免费下载链接】vcredist AIO Repack for latest Microsoft Visual C Redistributable Runtimes 项目地址: https://gitcode.com/gh_mirrors/vc/vcredist 你是否曾经遇到过这样的情况:下载了…

2026/7/6 0:05:19 阅读更多 →

周新闻

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容

B站视频下载神器BiliTools:5分钟学会轻松保存任何B站内容 【免费下载链接】BiliTools A cross-platform bilibili toolbox. 跨平台哔哩哔哩工具箱,支持下载视频、番剧等等各类资源 项目地址: https://gitcode.com/GitHub_Trending/bilit/BiliTools …

2026/7/6 8:11:50 阅读更多 →
威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型全解析:从新手入门到实战应用,助你构建安全产品!

威胁模型的陌生现状在忙碌疲惫的一天里,参与了关于混合后量子密码学的讨论,应付端点攻击找茬的人,还参与留言板讨论后,发现“威胁模型”对多数人仍是陌生概念,且多被当作时髦用语。有趣的相关画作有一幅由 Embyr 创作的…

2026/7/6 8:11:52 阅读更多 →
渗透测试入门指南:从零基础到实战环境搭建

渗透测试入门指南:从零基础到实战环境搭建

1. 从“看热闹”到“入门”:我理解的渗透测试到底是什么?每次看到新闻里说某个大公司的数据被“黑”了,或者某个网站被攻击导致服务瘫痪,你是不是和我一样,心里会冒出两个念头:一是“这黑客真厉害”&#x…

2026/7/6 6:52:56 阅读更多 →

月新闻