• SPI机制


    1、概念

            spi全称为 (Service Provider Interface),是JDK内置的一种服务提供发现机制。SPI是一种动态替换发现的机制,一种解耦非常优秀的思想。它是jdk提供给“服务提供厂商”或者“插件开发者”使用的接口,是一种扩展机制。

    2、用途

            在面向对象的设计中,模块之间我们一般会采取面向接口编程的方式,而在实际编程过程过程中,API的实现是封装在jar中,当我们想要换一种实现方法时,还要生成新的jar替换以前的实现类。而通过jdk的SPI机制就可以实现,首先不需要修改原来作为接口的jar的情况下,将原来实现的那个jar替换为另外一种实现的jar即可。

            总结一下SPI的思想:在系统的各个模块中,往往有不同的实现方案,例如日志模块的方案、xml解析的方案等,为了在装载模块的时候不具体指明实现类,我们需要一种服务发现机制,java spi就提供这样一种机制。有点类似于IoC的思想,将服务装配的控制权移到程序之外,在模块化设计时尤其重要。

    示例:重写第三方jar中SPRING BEAN的方式_xixingzhe2的博客-CSDN博客

    3、优缺点

    3.1 优点

    • 为了解耦,将接口和具体实现分离开来。
    • 提高框架的扩展性。应用程序可以根据实际业务情况启用框架扩展或替换框架组件,避免接口和实现都写在一起,调用方无权选择使用具体的实现类。

    3.2 缺点

    • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
    • 多个并发多线程使用ServiceLoader类的实例是不安全的。

    4、规范

            定义服务的通用接口,针对通用的服务接口,提供具体的实现类。

    1. 在jar包(服务提供者)的META-INF/services/目录中,新建一个文件,文件名为SPI接口的"全限定名"(如:com.ybw.spi.service.DemoService)。 文件内容为该接口的具体实现类的"全限定名"(如:com.ybw.spi.service.impl.DemoOneServiceImpl)。
    2. 将spi所在jar放在主程序的classpath中。
    3. 服务调用方使用java.util.ServiceLoader去动态加载具体的实现类到JVM中。

    5、SPI示例

    代码结构

    模块依赖关系

     5.1 spi-service

    定义接口

    1. package com.ybw.spi.service;
    2. /**
    3. * Demo service. Implements should be use SPI.
    4. *
    5. * @author ybw
    6. * @version V1.0
    7. * @className DemoService
    8. * @date 2022/6/29
    9. **/
    10. public interface DemoService {
    11. String sayHello();
    12. }

     5.2 spi-service-impl-one

    实现一

    1. package com.ybw.spi.service.impl;
    2. import com.ybw.spi.service.DemoService;
    3. /**
    4. * Implement for DemoService
    5. *
    6. * @author ybw
    7. * @version V1.0
    8. * @className DemoOneServiceImpl
    9. * @date 2022/6/29
    10. **/
    11. public class DemoOneServiceImpl implements DemoService {
    12. @Override
    13. public String sayHello() {
    14. return "hello world one";
    15. }
    16. }

    META-INF/services/下创建文件com.ybw.spi.service.DemoService,内容为

    com.ybw.spi.service.impl.DemoOneServiceImpl

     5.3 spi-service-impl-two

    实现二

    1. package com.ybw.spi.service.impl;
    2. import com.ybw.spi.service.DemoService;
    3. /**
    4. * Implement for DemoService
    5. *
    6. * @author ybw
    7. * @version V1.0
    8. * @className DemoOneServiceImpl
    9. * @date 2022/6/29
    10. **/
    11. public class DemoTwoServiceImpl implements DemoService {
    12. @Override
    13. public String sayHello() {
    14. return "hello world two";
    15. }
    16. }

    META-INF/services/下创建文件com.ybw.spi.service.DemoService,内容为

    com.ybw.spi.service.impl.DemoTwoServiceImpl

    5.4 spi-execute

    执行模块

    1. package com.ybw.spi.test;
    2. import com.ybw.spi.service.DemoService;
    3. import com.ybw.spi.service.impl.DemoOneServiceImpl;
    4. import lombok.extern.slf4j.Slf4j;
    5. import org.junit.jupiter.api.Test;
    6. import java.util.ServiceLoader;
    7. /**
    8. * 加载使用
    9. *
    10. * @author ybw
    11. * @version V1.0
    12. * @className SpiTest
    13. * @date 2022/6/29
    14. **/
    15. @Slf4j
    16. public class SpiTest {
    17. /**
    18. * @methodName: spiTest
    19. * @return: void
    20. * @author: ybw
    21. * @date: 2022/6/29
    22. **/
    23. @Test
    24. public void spiTest() {
    25. ServiceLoader<DemoService> demoServices = ServiceLoader.load(DemoService.class);
    26. demoServices.forEach(demoService -> {
    27. log.info(demoService.getClass().getName());
    28. log.info("ClassLoader:{}",demoService.getClass().getClassLoader());
    29. log.info(demoService.sayHello());
    30. });
    31. }
    32. }

    引入依赖spi-service-impl-one

     输入日志

    1. [INFO ] 2022-06-29 16:12:58.135 [main] com.ybw.spi.test.SpiTest - com.ybw.spi.service.impl.DemoOneServiceImpl
    2. [INFO ] 2022-06-29 16:12:58.146 [main] com.ybw.spi.test.SpiTest - ClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
    3. [INFO ] 2022-06-29 16:12:58.151 [main] com.ybw.spi.test.SpiTest - hello world one

    引入的实现模块不同,打印的日志也会不同。

    5.5 代码地址

    https://gitee.com/xixingzhe2/share/tree/master/spi/spi-jdk-demo

    6、spring spi

            在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

    7、jdk与spring spi区别

    jdkspring
    加载类不同JDK使用的工具类是ServiceLoaderSpring中使用的类是SpringFactoriesLoader,在 org.springframework.core.io.support包中
    文件路径不同jdk配置放在META-INF/services/目录中spring配置放在 META-INF/spring.factories中

  • 相关阅读:
    一心为农,以智取胜——张继群的智慧农业之路
    Java代理模式
    MySQL主从复制的实现步骤(超级详细哦)
    Kubernetes基础(二十八)-K8S调度之拓扑分布TopologySpreadConstraints
    Vue3.0项目——打造企业级音乐App(一)Tab栏、轮播图、歌单列表、滚动组件
    Springboot 跨域 session 不一致的问题
    【C++项目实现】推箱子
    百度智能云千帆大模型平台再升级,SDK版本开源发布!
    SparkSQL【核心编程、使用IDEA开发、用户自定义函数】
    命令行进入bios
  • 原文地址:https://blog.csdn.net/xixingzhe2/article/details/125522707