Skip to content

ManTouMT/SPI-demo

Repository files navigation

Java SPI 机制演示项目

这是一个针对Java SPI机制的完整演示项目,包含基础用法和多种典型场景的增强实现。

目录


SPI机制简介

什么是SPI?

SPI(Service Provider Interface)即"服务提供者接口",是一种服务发现机制。它将服务接口和具体实现分离,实现调用方和实现者的解耦,提升程序的扩展性和可维护性。

核心特点:

  • 接口由调用方定义,实现由服务提供方完成
  • 运行时动态发现和加载实现
  • 修改或替换实现无需修改调用方代码

典型应用:

  • JDBC数据库驱动加载
  • SLF4J日志框架
  • Spring框架扩展
  • Dubbo扩展机制

SPI vs API

img.png

核心区别:接口提供方变为了调用方

SPI运行机制

  1. META-INF/services 目录下创建以接口全类名命名的文件
  2. 文件内容为实现类的全类名
  3. ServiceLoader 类负责发现和加载实现

img_2.png


项目结构

SPI-demo/
├── SPI-Interface/          # 接口定义模块
│   └── src/java/com/lxy/demo/define/
│       ├── JavaLogService.java           # SPI接口定义
│       ├── LoggerService.java            # 服务门面类(增强版)
│       ├── DefaultJavaLogService.java    # 默认实现
│       ├── annotation/
│       │   ├── Priority.java             # 优先级注解
│       │   └── ServiceName.java          # 服务名称注解
│       └── loader/
│           ├── EnhancedServiceLoader.java # 增强服务加载器
│           └── CompositeLogService.java   # 组合服务(广播模式)
│
├── SPI-Logback-Service/    # Logback实现模块
│   └── src/java/com/lxy/demo/impl/
│       └── LogbackService.java           # @Priority(1) @ServiceName("logback")
│
├── SPI-Sl4j-Service/       # SL4J实现模块
│   └── src/java/com/lxy/demo/impl/
│       └── SL4JService.java              # @Priority(0) @ServiceName("sl4j")
│
└── SPI-Client/             # 客户端使用模块
    └── src/java/com/lxy/demo/client/
        └── Main.java                     # 典型场景演示

三方角色:

  1. 接口定义方:SPI-Interface
  2. 接口实现方:SPI-Logback-Service, SPI-Sl4j-Service
  3. 接口使用方:SPI-Client

增强功能

1. 优先级机制 @Priority

@Priority(0)  // 数值越小优先级越高
public class SL4JService implements JavaLogService { }

@Priority(1)
public class LogbackService implements JavaLogService { }

2. 服务命名 @ServiceName

@ServiceName("sl4j")
public class SL4JService implements JavaLogService { }

@ServiceName("logback")
public class LogbackService implements JavaLogService { }

3. 增强服务加载器 EnhancedServiceLoader

  • 自动按优先级排序
  • 支持按名称获取
  • 内置缓存机制
  • 支持默认实现

4. 组合模式 CompositeLogService

  • 广播调用所有实现
  • 适用于责任链、事件通知等场景

典型使用场景

场景1:基础用法 - 获取优先级最高的实现

// 方式1: 通过门面类
LoggerService logger = LoggerService.getService();
logger.infoLog("Hello SPI");

// 方式2: 直接获取主服务
JavaLogService primary = LoggerService.getPrimaryService();
primary.warnLog("Warning message");

场景2:按名称获取特定实现

// 通过@ServiceName注解的名称
JavaLogService sl4j = LoggerService.getServiceByName("sl4j");
JavaLogService logback = LoggerService.getServiceByName("logback");

// 通过类名
JavaLogService service = LoggerService.getServiceByName("SL4JService");

场景3:组合模式 - 广播调用所有实现

// 消息会被所有实现处理
JavaLogService composite = LoggerService.getCompositeService();
composite.errorLog("这条消息会被所有实现处理");

场景4:遍历所有实现

List<JavaLogService> allServices = LoggerService.getAllServices();
for (JavaLogService service : allServices) {
    service.infoLog("来自 " + service.getClass().getSimpleName());
}

场景5:默认实现兜底

// 当没有SPI实现时自动使用默认实现
JavaLogService defaultService = LoggerService.getDefaultService();
defaultService.infoLog("默认实现输出");

场景6:可选获取(避免异常)

// 使用Optional避免服务不存在时抛出异常
Optional<JavaLogService> service = LoggerService.getServiceByNameOptional("sl4j");
service.ifPresent(s -> s.infoLog("服务存在"));

// 使用orElse提供备选
JavaLogService s = LoggerService.getServiceByNameOptional("not-exist")
        .orElse(LoggerService.getDefaultService());

场景7:服务发现与诊断

// 检查服务状态
System.out.println("是否有可用服务: " + LoggerService.hasService());
System.out.println("可用服务数量: " + LoggerService.getServiceCount());

// 打印所有可用服务信息
LoggerService.printAvailableServices();

快速开始

运行演示

# 编译并运行Client模块的Main类
# 输出将展示所有7种典型场景的使用方式

预期输出

========================================
    Java SPI 典型场景用法演示
========================================

【场景1】基础用法 - 获取优先级最高的实现
----------------------------------------
[SL4J-INFO] 通过门面类调用
[SL4J-WARN] 直接获取主服务调用

【场景2】按名称获取特定实现
----------------------------------------
[SL4J-INFO] 指定使用SL4J实现
[LOGBACK-INFO] 指定使用Logback实现
...

注意事项

  1. 无参构造方法:接口实现类必须提供无参构造方法
  2. META-INF目录:在IDEA中必须将resources目录标记为资源根目录
  3. 服务配置文件:文件名必须是接口的全类名,内容是实现类的全类名

在IDEA中打jar包的步骤

1. 在项目结构中添加要打的包

img_3.png

2. 具体配置

img_4.png

配置要点:

  • 只选择需要打包的模块
  • 指定主类作为入口(有main方法的类)
  • "来自库的jar文件的配置"说明:
    • 第一个选项:模块class和依赖class打在一起 img_7.png
    • 第二个选项:模块和依赖分开打包 img_8.png
  • 一定要选定到META-INF目录

3. 选择构建

img_9.png img_10.png

4. 构建产物

img_11.png


扩展阅读

About

一个简单的SPI的demo

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •  

Languages