一文搞懂Java、Spring、SpringBoot SPI机制
文章目录一、先搞懂SPI到底是干啥的二、最基础JDK原生Java SPI1、原生SPI核心规则2、Java SPI完整实战示例① 第一步定义支付统一接口② 第二步写两个接口实现类③ 第三步创建SPI配置文件关键④ 第四步ServiceLoader加载测试3、运行结果 \ 原生SPI致命缺点三、进阶版Spring SPI改良增强版Java SPI1、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模式是只定义统一接口定规范多个不同业务写不同实现做插件配置文件里声明用哪个实现配关系框架/程序运行时自动扫描配置加载对应的实现类一句话总结接口我定义实现别人写配置配一下自动就加载。这就是所有SPI的核心思想不管是Java原生、Spring还是SpringBoot底层逻辑全是这个只是用法和优化程度不一样。二、最基础JDK原生Java SPI1、原生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加载接口所有配置的实现类ServiceLoaderPayServiceserviceLoaderServiceLoader.load(PayService.class);// 遍历所有实现直接调用方法for(PayServicepayService:serviceLoader){payService.pay(99);}}}3、运行结果 amp; 原生SPI致命缺点运行输出微信支付成功支付金额99元 支付宝支付成功支付金额99元效果看着还行但实际开发压根没人直接用Java原生SPI缺点太致命一次性加载所有实现类不能按需加载不管用不用都实例化浪费内存没有依赖注入实现类不能管理Bean没法整合Spring不能按名称、条件精准选择某个实现只能全遍历报错不友好排查问题极其麻烦所以Spring看不下去了自己搞了一套Spring SPI优化原生所有痛点。三、进阶版Spring SPI改良增强版Java SPI1、Spring SPI核心改动Spring SPI底层思想和Java原生一模一样还是接口配置文件自动加载只是改了两个核心点更好用、适配Spring配置文件路径改了老式META\-INF/spring\.factoriesSpringBoot2.7之前核心加载工具类换成SpringFactoriesLoader支持按需加载、Spring容器整合、优先级排序功能更强核心作用Spring底层扩展、框架初始化、加载内置组件全靠它。2、Spring SPI快速演示简单易懂接口和实现类不用改还是上面的PayService、微信、支付宝支付实现。① 新建spring.factories配置文件resources下新建META\-INF/spring\.factories格式是keyvalue# 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核心加载器ListPayServicepayServicesSpringFactoriesLoader.loadFactories(PayService.class,null);for(PayServicepayService:payServices){payService.pay199);}}}运行效果和原生一样但Spring SPI可以后续结合Spring容器管理支持Bean注入扩展性强很多。四、重点核心SpringBoot新版SPI现在项目主流用法1、关键知识点新旧版本区别SpringBoot 2.7版本是分水岭2.7之前用spring.factories老式SPI2.7及以后废弃spring.factories改用新版SPI配置新版SpringBoot SPI最大优势专门给自动配置、插件扩展量身定做结构更清晰性能更好按需加载。2、SpringBoot SPI核心规则配置文件改路径META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件里直接写需要自动加载的配置类/扩展类全路径SpringBoot启动时自动读取自动注册为Bean无需手动Bean3、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){ConfigurableApplicationContextcontextSpringApplication.run(SpringBootSpiApplication.class,args);// 从Spring容器中获取所有SmsService接口的实现BeanMapString,SmsServicebeanMapcontext.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只是面试题工作中天天都在用只是你没感知SpringBoot自动配置不用手动配Tomcat、Mvc、数据源全靠SPI自动加载配置类自定义Starter开发自己写通用组件别的项目引入就自动生效靠SPI插件化业务扩展支付、短信、OSS存储多厂商切换不用改代码改配置就行框架中间件扩展MyBatis、Redis、MQ各种扩展实现都是SPI思想七、最后简单总结所有SPI核心思想都一样接口定义实现分离配置驱动自动加载。Java原生SPI基础垫底缺点多业务基本不用。Spring SPI做了优化适配Spring容器旧版SpringBoot在用。现在开发直接学SpringBoot新版SPI适配最新版本做自动配置、自定义Starter必备。SPI终极目的解耦代码不用改核心逻辑就能无限扩展功能。