
一、前言到底什么是SPISPI全称Service Provider Interface服务提供者接口核心思想就8个字接口与实现完全解耦。不用修改框架原有源码只需要提前写好接口后续新增功能实现类、改个配置文件框架就能自动发现并加载新功能这就是SPI的核心价值。主流技术栈底层全都在用SPIJDBC驱动加载Dubbo分布式扩展组件SpringBoot全自动装配机制各类中间件自定义插件拓展目前行业主流就两套SPI方案Java原生SPI Spring升级版SPI下面从零拆解全程无晦涩难懂的废话。二、Java原生SPIJDK自带简陋但必学1、核心强制规则Java SPI是JDK内置原生能力不用额外导依赖但有三条死规则写错直接失效固定文件夹必须放在 resources/META-INF/services/ 目录下目录不能改名、不能换位置文件名硬性要求文件名必须和接口完整全类名一模一样多一个点、少一个字母都找不到文件内容规范里面填写当前接口所有实现类的完整全类名一行写一个换行分隔2、完整实战代码演示第一步定义通用业务接口// 统一支付渠道标准拓展接口public interface PayChannelSPI {// 通用支付下单核心方法void unifiedPay();}第二步编写多个业务实现类// 实现类1微信支付渠道专属实现public class WechatPayImpl implements PayChannelSPI {Overridepublic void unifiedPay() {System.out.println( 调用微信支付SDK发起微信快捷下单支付);}}// 实现类2支付宝支付渠道专属实现public class AliPayImpl implements PayChannelSPI {Overridepublic void unifiedPay() {System.out.println( 调用支付宝开放平台发起支付宝当面付扣款);}}第三步创建核心SPI配置文件关键步骤在项目资源目录下严格创建对应路径及文件resources/META-INF/services/com.pay.spi.PayChannelSPI⚠️ 重点文件名 接口全类名绝对不能乱写文件内部写入两个实现类的全类名com.pay.spi.WechatPayImplcom.pay.spi.AliPayImpl第四步编写测试类运行原生SPI加载逻辑import java.util.ServiceLoader;public class JavaPaySPITest {public static void main(String[] args) {// JDK原生API绑定支付渠道拓展接口ServiceLoaderPayChannelSPI serviceLoader ServiceLoader.load(PayChannelSPI.class);// 自动加载所有支付实现批量执行下单逻辑for (PayChannelSPI payChannelSPI : serviceLoader) {payChannelSPI.unifiedPay();}}}运行结果 调用微信支付SDK发起微信快捷下单支付 调用支付宝开放平台发起支付宝当面付扣款3、Java SPI底层怎么精准找到对应文件解答核心疑问很多人疑惑有几十个接口、几十个配置文件它会不会找乱、会不会全加载答案绝对不会乱也不会加载无关文件底层执行逻辑全自动精准匹配你代码里传入 VideoPlaySPI.class底层自动提取该接口的完整全类名自动拼接固定前缀路径META-INF/services/ 接口全类名只精准读取这一个同名文件其他所有接口的SPI文件一概不扫描、不加载、不读取4、Java原生SPI致命缺点原生SPI能用但企业开发没人直接用全是硬伤❌ 不能按需挑选实现类配置文件里写了几个实现类就必须全部加载、全部实例化想单独只用某一个实现完全做不到❌ 无法自定义执行顺序只能按文件里书写顺序加载不能手动调整优先级❌ 配置文件极度分散混乱一个接口对应一个独立配置文件接口多了之后services目录下全是零散文件后期维护极其麻烦❌ 加载性能差采用懒加载迭代器遍历频繁反射创建对象没有缓存机制三、Spring SPI针对性优化企业主流方案Spring没有推翻Java原生SPI的核心思想只是全套升级优化完美解决原生SPI所有痛点SpringBoot自动配置底层全靠它支撑。1、Spring SPI核心优化亮点✅ 统一配置文件所有接口、所有实现类全部写在同一个 spring.factories 文件里整洁好维护✅ 支持按需指定实现想加载哪个就加载哪个不用全部强制加载✅ 支持自定义排序优先级配合注解就能调整实现类执行先后顺序✅ 自带缓存机制加载过的实现类直接缓存提升底层运行性能2、Spring SPI完整实战案例第一步自定义数据库拓展接口通用标准// 全平台消息推送统一拓展接口public interface MessagePushSPI {// 统一消息批量推送核心方法void sendPushMessage();}第二步编写多数据库场景实现类// 实现1APP站内弹窗消息推送实现public class AppPushImpl implements MessagePushSPI {Overridepublic void sendPushMessage() {System.out.println( 成功推送APP弹窗通知全员触达在线用户);}}// 实现2短信验证码营销消息推送实现public class SmsPushImpl implements MessagePushSPI {Overridepublic void sendPushMessage() {System.out.println( 成功调用短信网关批量下发业务通知短信);}}第三步创建Spring专属配置文件路径同样放在resources/META-INF/ 目录下新建固定文件名spring.factories全局就这一个配置文件文件内标准key-value格式编写# key 消息推送接口完整全类名# value 多个推送渠道实现类逗号隔开com.message.spi.MessagePushSPIcom.message.spi.AppPushImpl,com.message.spi.SmsPushImpl# ✅ 补充重点项目有【多个接口、多组实现类】时统一往下续写即可# 核心规则不同接口换行分开写key单独绑定对应自己的实现类互不干扰# 额外示例新增支付渠道接口、文件上传接口全部写在这同一个文件里# com.pay.spi.PayChannelSPIcom.pay.spi.WechatPayImpl,com.pay.spi.AliPayImpl# com.upload.spi.FileUploadSPIcom.upload.spi.OssUploadImpl,com.upload.spi.LocalUploadImpl第四步Spring工具类直接加载测试import org.springframework.core.io.support.SpringFactoriesLoader;import java.util.List;public class SpringMessageSPITest {public static void main(String[] args) {// Spring专用加载工具加载所有消息推送实现类ListMessagePushSPI spiList SpringFactoriesLoader.loadFactories(MessagePushSPI.class,Thread.currentThread().getContextClassLoader());// 遍历执行全渠道消息推送for (MessagePushSPI pushSPI : spiList) {pushSPI.sendPushMessage();}}}运行效果 成功推送APP弹窗通知全员触达在线用户 成功调用短信网关批量下发业务通知短信四、核心重点对比面试直接背满分答案对比维度Java 原生 SPISpring SPIspring.factories配置文件数量一个接口对应一个独立文件零散混乱全局统一仅一个 spring.factories 文件整洁易维护加载方式只能一次性加载全部实现类无法按需挑选支持按需加载单个实现也可批量加载灵活度高执行顺序固定按文件书写顺序无法手动调整排序支持 Order 注解自定义优先级灵活控制执行顺序底层工具类JDK原生 ServiceLoaderSpring专属 SpringFactoriesLoader性能体验无缓存频繁反射性能较差自带本地缓存机制反复加载性能更强适用场景简单小型工具项目极少单独使用SpringBoot、中间件、企业级核心框架必备五、底层核心原理一句话总结1. Java SPI靠「固定文件夹文件名接口全类名」精准匹配强制全量加载简单但短板明显适合入门学习2. Spring SPI沿用SPI解耦核心思想统一配置、优化加载逻辑新增排序缓存按需加载能力是SpringBoot自动配置的底层基石六、总结SPI是服务提供发现机制核心实现接口与实现类解耦提升框架扩展性。Java原生SPI配置分散、只能全量加载、无法排序Spring SPI优化为统一spring.factories配置文件支持按需加载、自定义排序、自带缓存性能更强SpringBoot自动装配底层依赖Spring SPI实现。