学习Java SPI机制的项目

yang yi 5ef81728c1 doc:添加readme文档 1 miesiąc temu
gradle dff23a5133 feat:学习Java SPI机制项目初始化 1 miesiąc temu
simple-api e2e150a3f8 feat:Service模块 1 miesiąc temu
simple-company e0aedbeffb fix:添加Gradle的application插件,使用gradle命令的方式运行Java程序,并指定UTF-8编码,防止程序输出乱码 1 miesiąc temu
simple-isp-moblie a55de0bf19 feat:Service Provider模块的配置文件(中国移动网络提供),在META-INF/services下创建文件space.anyi.InternetService,内容为:space.anyi.MoblieInternetService 1 miesiąc temu
simple-isp-unicom b90d1232fa feat:Service Provider模块的配置文件(中国联通网络提供),在META-INF/services下创建文件space.anyi.InternetService,内容为:space.anyi.UnicomInternetService 1 miesiąc temu
.gitignore fafe7de0e4 feat:模块simple-company使用Service Loader加载服务,然后使用Service Provider提供的对应服务 1 miesiąc temu
build.gradle e0aedbeffb fix:添加Gradle的application插件,使用gradle命令的方式运行Java程序,并指定UTF-8编码,防止程序输出乱码 1 miesiąc temu
gradlew dff23a5133 feat:学习Java SPI机制项目初始化 1 miesiąc temu
gradlew.bat dff23a5133 feat:学习Java SPI机制项目初始化 1 miesiąc temu
readme.md 5ef81728c1 doc:添加readme文档 1 miesiąc temu
settings.gradle fafe7de0e4 feat:模块simple-company使用Service Loader加载服务,然后使用Service Provider提供的对应服务 1 miesiąc temu

readme.md

java SPI机制

参考bilibili的视频

Service Provider Interface

java1.6引入的,基于ClassLoader的发现并加载服务的机制

核心概念

  • Service:提供服务的接口,服务的标准
  • Service Provider:服务的具体实现,服务的提供者
  • Service Loader:服务的加载器,通过Server Loader加载具体的服务实现

SPI的工作机制

JDBC技术典型的SPI实践案例,JDK通过Service接口,具体的数据库厂商负责具体的Service Provider实现

  1. 应用程序启动时通过Service Loader加载第三方jar中的Service Provider(Service的具体实现类)
  2. 在应用程序中通过Service的形式使用,不用关心具体的实现细节

SPI规范

  1. 配置文件规范

    • 文件路径:必须在jar包中META-INF/services目录下
    • 文件名称:Service接口的全限定名
    • 文件内容:Service Provider实现类的全限定名,每个实现类单独占一行

e.g

在MySQL的驱动jar中的META-INF/services目录中有一个"java.sql.Driver"文件,文件内容为"com.mysql.cj.jdbc.Driver"

  1. Service Provide类必须具备无参的构造方法

    • 因为使用反射技术创建实现类时需要使用无参构造器
  2. 保证能加载到配置文件和Service

    • 方式一:将Service Provider的jar包放到classpath中
    • 方式二:将jar放到jre的扩展目录中
    • 方式三:自定义一个ClassLoader

SPI实现案例

一个公司A,需要连接互联网.公司A定义连接网络的API(Service),由中国移动(Service Provider)和中国联通(Serivce Provider)负责提供具体的网络连接服务

SPI实现流程

  1. 在service定义接口
  2. 在service provider中实现service,并在META-INF/services目录下提供对应的配置文件
  3. 在应用程序中使用ServiceLoader加载service,然后进行使用

定义service接口

  1. 定义service接口

    • 在模块Simple-api中定义网络的连接接口

      package space.anyi;
      
      /**
      * @ProjectName: SPI-learn
      * @FileName: InternetService
      * @Author: 杨逸
      * @Data:2025/10/13 11:13
      * @Description: 网络服务接口
      */
      public interface InternetService {
      void connect();
      }
      
      

Service Provider实现Service接口

  1. 模块simple-isp-moblie

    • 模块simple-isp-moblie引入simple-api依赖

      plugins {
      id 'java'
      }
      
      group 'space.anyi'
      version '1.0-SNAPSHOT'
      
      repositories {
      mavenCentral()
      }
      
      dependencies {
      //引入simple-api依赖,以项目的形式
      implementation(project(":simple-api"))
      testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
      testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
      }
      
      test {
      useJUnitPlatform()
      }
      
      
  • 模块simple-isp-moblie实现service

    package space.anyi;
    
    /**
    * @ProjectName: SPI-learn
    * @FileName: MoblieInternetService
    * @Author: 杨逸
    * @Data:2025/10/13 11:24
    * @Description: 中国移动提供的网络服务实现
    */
    public class MoblieInternetService implements InternetService{
    
    @Override
    public void connect() {
        System.out.println("使用中国移动提供的网络服务");
    }
    }
    
    
  • 在"META-INF/services"目录下提供"space.anyi.InternetService"配置文件

    space.anyi.MoblieInternetService
    
  1. 模块simple-isp-unicom
  • 模块simple-isp-unicom引入simple-api依赖

    plugins {
      id 'java'
    }
      
    group 'space.anyi'
    version '1.0-SNAPSHOT'
      
    repositories {
      mavenCentral()
    }
      
    dependencies {
      //引入simple-api依赖,以项目的形式
      implementation(project(":simple-api"))
      testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
      testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
    }
      
    test {
      useJUnitPlatform()
    }
      
    
  • 模块simple-isp-unicom实现service

    package space.anyi;
    
    /**
    * @ProjectName: SPI-learn
    * @FileName: UnicomInternetService
    * @Author: 杨逸
    * @Data:2025/10/13 11:37
    * @Description: 中国联通提供的网络服务实现
    */
    public class UnicomInternetService implements InternetService{
    @Override
    public void connect() {
        System.out.println("使用中国联通提供的网络服务");
    }
    }
    
    

在"META-INF/services"目录下提供"space.anyi.InternetService"配置文件

space.anyi.UnicomInternetService

Service Loader加载Service

  • 模块simple-compnay引入依赖

    plugins {
      id 'java'
    }
      
    group 'space.anyi'
    version '1.0-SNAPSHOT'
      
    repositories {
      mavenCentral()
    }
      
    dependencies {
      //可以全都使用,也可以按需使用
      //服务提供,使用中国移动的网络服务
      implementation(project(":simple-isp-moblie"))
      //服务提供,使用中国联通的网络服务
      implementation(project(":simple-isp-unicom"))
      //接口依赖
      implementation(project(":simple-api"))
      testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
      testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
    }
      
    test {
      useJUnitPlatform()
    }
      
    
  • 使用加载Service并使用

    package space.anyi;
      
    import java.util.ServiceLoader;
      
    /**
    * @ProjectName: SPI-learn
    * @FileName: CompanyApplication
    * @Author: 杨逸
    * @Data:2025/10/13 11:45
    * @Description: 公司A连接互联网的应用
    */
    public class CompanyApplication {
      public static void main(String[] args) {
          //通过java.util.ServiceLoader加载服务
          ServiceLoader<InternetService> services = ServiceLoader.load(InternetService.class);
          //遍历服务
          for (InternetService service : services) {
              //使用服务
              service.connect();
          }
      
      }
    }
      
    

运行项目实现开箱即用的效果

SPI总结

  • 作用:提供了一种组件发现和注册的方式,可以用于实现各种插件,或者灵活替换框架使用的组件
  • 优点:基于面向接口编程,优雅地实现模块之间的解藕
  • 设计思想:面向接口+配置文件+反射技术
  • 应用场景:JDBC,SLF4J,Servlet容器初始化等等