1、概念

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

2、用途

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

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

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示例

代码结构

java中的spi机制 java spi机制的优缺点_java中的spi机制

模块依赖关系

java中的spi机制 java spi机制的优缺点_java中的spi机制_02

 5.1 spi-service

定义接口

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();

}

 5.2 spi-service-impl-one

实现一

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

 5.3 spi-service-impl-two

实现二

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

5.4 spi-execute

执行模块

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

java中的spi机制 java spi机制的优缺点_服务器_03

 输入日志

[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

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

5.5 代码地址

6、spring spi

        在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。

        从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回。注意的是,其实这里不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包,只不过这个文件只会在Classpath下的jar包中。

7、jdk与spring spi区别

jdk

spring

加载类不同

JDK使用的工具类是ServiceLoader

Spring中使用的类是SpringFactoriesLoader,在 org.springframework.core.io.support包中

文件路径不同

jdk配置放在META-INF/services/目录中

spring配置放在 META-INF/spring.factories中