news 2026/4/30 16:07:59

一文搞懂Java、Spring、SpringBoot SPI机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一文搞懂Java、Spring、SpringBoot SPI机制

文章目录

    • 一、先搞懂:SPI到底是干啥的?
    • 二、最基础:JDK原生Java SPI
      • 1、原生SPI核心规则
      • 2、Java SPI完整实战示例
        • ① 第一步:定义支付统一接口
        • ② 第二步:写两个接口实现类
        • ③ 第三步:创建SPI配置文件(关键)
        • ④ 第四步:ServiceLoader加载测试
      • 3、运行结果 \& 原生SPI致命缺点
    • 三、进阶版:Spring SPI(改良增强版Java SPI)
      • 1、Spring SPI核心改动
      • 2、Spring SPI快速演示(简单易懂)
        • ① 新建spring\.factories配置文件
        • ② SpringFactoriesLoader加载测试
    • 四、重点核心:SpringBoot新版SPI(现在项目主流用法)
      • 1、关键知识点:新旧版本区别
      • 2、SpringBoot SPI核心规则
      • 3、SpringBoot SPI完整实战
        • ① 第一步:新建SpringBoot项目,无需额外依赖
        • ② 定义短信服务统一接口
        • ③ 编写两个实现类
        • ④ 新建SpringBoot新版SPI核心配置文件
        • ⑤ 启动类测试注入使用
      • 4、运行效果
    • 五、三者核心区别一张表看懂(面试必背)
    • 六、实际开发中SPI到底有啥用?
    • 七、最后简单总结

很多后端小伙伴面试总被问:Java SPI是什么?Spring SPI和原生SPI有啥区别?SpringBoot自动配置为啥靠SPI就能不用手动new对象?

平时写业务代码,咱们天天@Autowired注入Bean,习惯了Spring容器帮我们管理所有对象。但大家有没有想过一个问题:

Spring框架本身、SpringBoot各种starter,压根不知道我们后续会写什么业务实现,为啥启动就能自动加载各种扩展功能、自动装配配置类?

答案核心就两个字:SPI

今天从JDK原生SPI讲起,再到Spring SPI,最后落地SpringBoot新版SPI实战,由浅入深,每个都给完整可运行demo,看完不仅懂原理,还知道工作中啥时候用、怎么用。


一、先搞懂:SPI到底是干啥的?

先别记英文全称,不用背官方术语。

SPI本质就一件事:接口和实现解耦,不用改核心代码,通过配置就能切换、新增插件式实现。

咱们平时开发是:写接口 → 写实现类 → 代码里直接new或者注入实现,写死了,换个实现就要改代码重启。

SPI模式是:

  1. 只定义统一接口(定规范)

  2. 多个不同业务写不同实现(做插件)

  3. 配置文件里声明用哪个实现(配关系)

  4. 框架/程序运行时自动扫描配置,加载对应的实现类

一句话总结:接口我定义,实现别人写,配置配一下,自动就加载。

这就是所有SPI的核心思想,不管是Java原生、Spring还是SpringBoot,底层逻辑全是这个,只是用法和优化程度不一样。


二、最基础:JDK原生Java SPI

1、原生SPI核心规则

JDK自带SPI,不用引任何依赖,开箱即用,规则就三条,很好记:

  • 第一步:定义统一业务接口

  • 第二步:写多个不同的实现类

  • 第三步:在固定目录META-INF/services/接口全类名建配置文件,文件里写实现类全路径

  • 第四步:用JDK自带ServiceLoader加载实现类,自动实例化调用

重点硬性要求:实现类必须有无参构造方法,不然反射实例化会报错。

2、Java SPI完整实战示例

咱们做一个简单场景:支付方式接口,两种实现:微信支付、支付宝支付,用SPI动态加载。

① 第一步:定义支付统一接口
// 支付业务统一接口publicinterfacePayService{// 统一下单支付方法voidpay(longmoney);}
② 第二步:写两个接口实现类
// 微信支付实现publicclassWechatPayServiceImplimplementsPayService{@Overridepublicvoidpay(longmoney){System.out.println("微信支付成功,支付金额:"+money+"元");}}// 支付宝支付实现publicclassAlipayServiceImplimplementsPayService{@Overridepublicvoidpay(longmoney){System.out.println("支付宝支付成功,支付金额:"+money+"元");}}
③ 第三步:创建SPI配置文件(关键)

在项目resources目录下,新建文件夹:META\-INF/services

新建文件,文件名必须是接口的全类名:比如com\.example\.spi\.PayService

文件内容:写入两个实现类的全类名,换行分隔:

com.example.spi.WechatPayServiceImpl com.example.spi.AlipayServiceImpl
④ 第四步:ServiceLoader加载测试
importjava.util.ServiceLoader;publicclassJavaSpiTest{publicstaticvoidmain(String[]args){// 核心:JDK原生ServiceLoader加载接口所有配置的实现类ServiceLoader<PayService>serviceLoader=ServiceLoader.load(PayService.class);// 遍历所有实现,直接调用方法for(PayServicepayService:serviceLoader){payService.pay(99);}}}

3、运行结果 &amp; 原生SPI致命缺点

运行输出:

微信支付成功,支付金额:99元 支付宝支付成功,支付金额:99元

效果看着还行,但实际开发压根没人直接用Java原生SPI,缺点太致命

  1. 一次性加载所有实现类,不能按需加载,不管用不用都实例化,浪费内存

  2. 没有依赖注入,实现类不能管理Bean,没法整合Spring

  3. 不能按名称、条件精准选择某个实现,只能全遍历

  4. 报错不友好,排查问题极其麻烦

所以Spring看不下去了,自己搞了一套Spring SPI,优化原生所有痛点。


三、进阶版:Spring SPI(改良增强版Java SPI)

1、Spring SPI核心改动

Spring SPI底层思想和Java原生一模一样,还是接口+配置文件+自动加载,只是改了两个核心点,更好用、适配Spring:

  • 配置文件路径改了:老式META\-INF/spring\.factories(SpringBoot2.7之前核心)

  • 加载工具类换成:SpringFactoriesLoader

  • 支持按需加载、Spring容器整合、优先级排序,功能更强

核心作用:Spring底层扩展、框架初始化、加载内置组件全靠它。

2、Spring SPI快速演示(简单易懂)

接口和实现类不用改,还是上面的PayService、微信、支付宝支付实现。

① 新建spring.factories配置文件

resources下新建META\-INF/spring\.factories,格式是key=value:

# key:接口全类名,value:多个实现类全类名逗号分隔 com.example.spi.PayService=\ com.example.spi.WechatPayServiceImpl,\ com.example.spi.AlipayServiceImpl
② SpringFactoriesLoader加载测试
importorg.springframework.core.io.support.SpringFactoriesLoader;importjava.util.List;publicclassSpringSpiTest{publicstaticvoidmain(String[]args){// Spring SPI核心加载器List<PayService>payServices=SpringFactoriesLoader.loadFactories(PayService.class,null);for(PayServicepayService:payServices){payService.pay199);}}}

运行效果和原生一样,但Spring SPI可以后续结合Spring容器管理,支持Bean注入,扩展性强很多。


四、重点核心:SpringBoot新版SPI(现在项目主流用法)

1、关键知识点:新旧版本区别

SpringBoot 2.7版本是分水岭

  • 2.7之前:用spring.factories老式SPI

  • 2.7及以后:废弃spring.factories,改用新版SPI配置

新版SpringBoot SPI最大优势:专门给自动配置、插件扩展量身定做,结构更清晰,性能更好,按需加载

2、SpringBoot SPI核心规则

  • 配置文件改路径:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

  • 文件里直接写需要自动加载的配置类/扩展类全路径

  • SpringBoot启动时自动读取,自动注册为Bean,无需手动@Bean

3、SpringBoot SPI完整实战

场景:做一个短信发送SPI扩展,默认阿里云短信,后续随时加腾讯云短信,不用改核心代码。

① 第一步:新建SpringBoot项目,无需额外依赖
② 定义短信服务统一接口
publicinterfaceSmsService{voidsendSms(Stringphone,Stringcontent);}
③ 编写两个实现类
// 阿里云短信实现publicclassAliSmsServiceImplimplementsSmsService{@OverridepublicvoidsendSms(Stringphone,Stringcontent){System.out.println("阿里云短信发送成功,手机号:"+phone+",内容:"+content);}}// 腾讯云短信实现publicclassTencentSmsServiceImplimplementsSmsService{@OverridepublicvoidsendSms(Stringphone,Stringcontent){System.out.println("腾讯云短信发送成功,手机号:"+phone+",内容:"+content);}}
④ 新建SpringBoot新版SPI核心配置文件

在resources下创建目录:META\-INF/spring

新建文件:org\.springframework\.boot\.autoconfigure\.AutoConfiguration\.imports

文件内容:写入需要自动加载的实现类全路径:

com.example.springspi.service.AliSmsServiceImpl com.example.springspi.service.TencentSmsServiceImpl
⑤ 启动类测试注入使用
importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.ConfigurableApplicationContext;importjava.util.Map;@SpringBootApplicationpublicclassSpringBootSpiApplication{publicstaticvoidmain(String[]args){ConfigurableApplicationContextcontext=SpringApplication.run(SpringBootSpiApplication.class,args);// 从Spring容器中获取所有SmsService接口的实现BeanMap<String,SmsService>beanMap=context.getBeansOfType(SmsService.class);// 遍历调用beanMap.values().forEach(smsService->smsService.sendSms("13800138000","您的验证码是888888"));}}

4、运行效果

阿里云短信发送成功,手机号:13800138000,内容:您的验证码是888888 腾讯云短信发送成功,手机号:13800138000,内容:您的验证码是888888

看到没?我们没有给实现类加任何@Component、@Bean注解,SpringBoot启动自动通过SPI配置加载,注册成Bean,直接就能注入使用


五、三者核心区别一张表看懂(面试必背)

SPI类型配置文件位置加载工具特点使用场景
Java原生SPIMETA-INF/services/接口名ServiceLoader全加载、无依赖、功能弱简单底层扩展,极少业务用
Spring老式SPIMETA-INF/spring.factoriesSpringFactoriesLoader适配Spring、支持排序旧版SpringBoot自动配置
SpringBoot新版SPIMETA-INF/spring/xxx.importsSpringBoot启动器自动加载性能好、按需加载、专门适配自动配置现在所有SpringBoot项目扩展、starter开发

六、实际开发中SPI到底有啥用?

不用觉得SPI只是面试题,工作中天天都在用,只是你没感知:

  1. SpringBoot自动配置:不用手动配Tomcat、Mvc、数据源,全靠SPI自动加载配置类

  2. 自定义Starter开发:自己写通用组件,别的项目引入就自动生效,靠SPI

  3. 插件化业务扩展:支付、短信、OSS存储多厂商切换,不用改代码,改配置就行

  4. 框架中间件扩展:MyBatis、Redis、MQ各种扩展实现,都是SPI思想


七、最后简单总结

  1. 所有SPI核心思想都一样:接口定义,实现分离,配置驱动,自动加载

  2. Java原生SPI基础垫底,缺点多,业务基本不用。

  3. Spring SPI做了优化,适配Spring容器,旧版SpringBoot在用。

  4. 现在开发直接学SpringBoot新版SPI,适配最新版本,做自动配置、自定义Starter必备。

  5. SPI终极目的:解耦代码,不用改核心逻辑,就能无限扩展功能

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/28 18:00:40

终极指南:如何高效使用SketchUp STL插件实现3D打印模型转换

终极指南&#xff1a;如何高效使用SketchUp STL插件实现3D打印模型转换 【免费下载链接】sketchup-stl A SketchUp Ruby Extension that adds STL (STereoLithography) file format import and export. 项目地址: https://gitcode.com/gh_mirrors/sk/sketchup-stl Sketc…

作者头像 李华
网站建设 2026/4/28 18:00:39

终极指南:如何将Stable Diffusion模型从10GB压缩到4GB的实战技巧

终极指南&#xff1a;如何将Stable Diffusion模型从10GB压缩到4GB的实战技巧 【免费下载链接】stable-diffusion A latent text-to-image diffusion model 项目地址: https://gitcode.com/GitHub_Trending/st/stable-diffusion Stable Diffusion作为一款强大的 latent t…

作者头像 李华
网站建设 2026/4/28 17:59:35

黑苹果终极简化指南:OpCore-Simplify三步快速配置教程

黑苹果终极简化指南&#xff1a;OpCore-Simplify三步快速配置教程 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 还在为黑苹果复杂的OpenCore配置而头…

作者头像 李华