spi全称为 (Service Provider Interface),是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制,一种解耦非常优秀的思想。它是jdk提供给“服务提供厂商”或者“插件开发者”使用的接口,是一种扩展机制。
在面向对象的设计中,模块之间我们一般会采取面向接口编程的方式,而在实际编程过程过程中,API的实现是封装在jar中,当我们想要换一种实现方法时,还要生成新的jar替换以前的实现类。而通过jdk的SPI机制就可以实现,首先不需要修改原来作为接口的jar的情况下,将原来实现的那个jar替换为另外一种实现的jar即可。
总结一下SPI的思想:在系统的各个模块中,往往有不同的实现方案,例如日志模块的方案、xml解析的方案等,为了在装载模块的时候不具体指明实现类,我们需要一种服务发现机制,java spi就提供这样一种机制。有点类似于IoC的思想,将服务装配的控制权移到程序之外,在模块化设计时尤其重要。
示例:重写第三方jar中SPRING BEAN的方式_xixingzhe2的博客-CSDN博客
定义服务的通用接口,针对通用的服务接口,提供具体的实现类。
代码结构

模块依赖关系

定义接口
- package com.ybw.spi.service;
-
-
- /**
- * Demo service. Implements should be use SPI.
- *
- * @author ybw
- * @version V1.0
- * @className DemoService
- * @date 2022/6/29
- **/
- public interface DemoService {
-
- String sayHello();
-
- }
实现一
- package com.ybw.spi.service.impl;
-
-
- import com.ybw.spi.service.DemoService;
-
- /**
- * Implement for DemoService
- *
- * @author ybw
- * @version V1.0
- * @className DemoOneServiceImpl
- * @date 2022/6/29
- **/
- public class DemoOneServiceImpl implements DemoService {
-
- @Override
- public String sayHello() {
- return "hello world one";
- }
- }
META-INF/services/下创建文件com.ybw.spi.service.DemoService,内容为
com.ybw.spi.service.impl.DemoOneServiceImpl
实现二
- package com.ybw.spi.service.impl;
-
-
- import com.ybw.spi.service.DemoService;
-
- /**
- * Implement for DemoService
- *
- * @author ybw
- * @version V1.0
- * @className DemoOneServiceImpl
- * @date 2022/6/29
- **/
- public class DemoTwoServiceImpl implements DemoService {
-
- @Override
- public String sayHello() {
- return "hello world two";
- }
- }
META-INF/services/下创建文件com.ybw.spi.service.DemoService,内容为
com.ybw.spi.service.impl.DemoTwoServiceImpl
执行模块
- package com.ybw.spi.test;
-
- import com.ybw.spi.service.DemoService;
- import com.ybw.spi.service.impl.DemoOneServiceImpl;
- import lombok.extern.slf4j.Slf4j;
- import org.junit.jupiter.api.Test;
-
- import java.util.ServiceLoader;
-
- /**
- * 加载使用
- *
- * @author ybw
- * @version V1.0
- * @className SpiTest
- * @date 2022/6/29
- **/
- @Slf4j
- public class SpiTest {
-
-
- /**
- * @methodName: spiTest
- * @return: void
- * @author: ybw
- * @date: 2022/6/29
- **/
- @Test
- public void spiTest() {
- ServiceLoader<DemoService> demoServices = ServiceLoader.load(DemoService.class);
- demoServices.forEach(demoService -> {
- log.info(demoService.getClass().getName());
- log.info("ClassLoader:{}",demoService.getClass().getClassLoader());
- log.info(demoService.sayHello());
- });
- }
- }
引入依赖spi-service-impl-one

输入日志
- [INFO ] 2022-06-29 16:12:58.135 [main] com.ybw.spi.test.SpiTest - com.ybw.spi.service.impl.DemoOneServiceImpl
- [INFO ] 2022-06-29 16:12:58.146 [main] com.ybw.spi.test.SpiTest - ClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
- [INFO ] 2022-06-29 16:12:58.151 [main] com.ybw.spi.test.SpiTest - hello world one
引入的实现模块不同,打印的日志也会不同。
https://gitee.com/xixingzhe2/share/tree/master/spi/spi-jdk-demo
在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。
从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回。注意的是,其实这里不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包,只不过这个文件只会在Classpath下的jar包中。
可以看另一边文章:spring boot 自定义starter_xixingzhe2的博客-CSDN博客
源代码:https://gitee.com/xixingzhe2/share/tree/master/spring/spring-demo-starter-parent
| jdk | spring | |
| 加载类不同 | JDK使用的工具类是ServiceLoader | Spring中使用的类是SpringFactoriesLoader,在 org.springframework.core.io.support包中 |
| 文件路径不同 | jdk配置放在META-INF/services/目录中 | spring配置放在 META-INF/spring.factories中 |