这是一个针对Java SPI机制的完整演示项目,包含基础用法和多种典型场景的增强实现。
SPI(Service Provider Interface)即"服务提供者接口",是一种服务发现机制。它将服务接口和具体实现分离,实现调用方和实现者的解耦,提升程序的扩展性和可维护性。
核心特点:
- 接口由调用方定义,实现由服务提供方完成
- 运行时动态发现和加载实现
- 修改或替换实现无需修改调用方代码
典型应用:
- JDBC数据库驱动加载
- SLF4J日志框架
- Spring框架扩展
- Dubbo扩展机制
核心区别:接口提供方变为了调用方
- 在
META-INF/services目录下创建以接口全类名命名的文件 - 文件内容为实现类的全类名
ServiceLoader类负责发现和加载实现
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 # 典型场景演示
三方角色:
- 接口定义方:SPI-Interface
- 接口实现方:SPI-Logback-Service, SPI-Sl4j-Service
- 接口使用方:SPI-Client
@Priority(0) // 数值越小优先级越高
public class SL4JService implements JavaLogService { }
@Priority(1)
public class LogbackService implements JavaLogService { }@ServiceName("sl4j")
public class SL4JService implements JavaLogService { }
@ServiceName("logback")
public class LogbackService implements JavaLogService { }- 自动按优先级排序
- 支持按名称获取
- 内置缓存机制
- 支持默认实现
- 广播调用所有实现
- 适用于责任链、事件通知等场景
// 方式1: 通过门面类
LoggerService logger = LoggerService.getService();
logger.infoLog("Hello SPI");
// 方式2: 直接获取主服务
JavaLogService primary = LoggerService.getPrimaryService();
primary.warnLog("Warning message");// 通过@ServiceName注解的名称
JavaLogService sl4j = LoggerService.getServiceByName("sl4j");
JavaLogService logback = LoggerService.getServiceByName("logback");
// 通过类名
JavaLogService service = LoggerService.getServiceByName("SL4JService");// 消息会被所有实现处理
JavaLogService composite = LoggerService.getCompositeService();
composite.errorLog("这条消息会被所有实现处理");List<JavaLogService> allServices = LoggerService.getAllServices();
for (JavaLogService service : allServices) {
service.infoLog("来自 " + service.getClass().getSimpleName());
}// 当没有SPI实现时自动使用默认实现
JavaLogService defaultService = LoggerService.getDefaultService();
defaultService.infoLog("默认实现输出");// 使用Optional避免服务不存在时抛出异常
Optional<JavaLogService> service = LoggerService.getServiceByNameOptional("sl4j");
service.ifPresent(s -> s.infoLog("服务存在"));
// 使用orElse提供备选
JavaLogService s = LoggerService.getServiceByNameOptional("not-exist")
.orElse(LoggerService.getDefaultService());// 检查服务状态
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实现
...
- 无参构造方法:接口实现类必须提供无参构造方法
- META-INF目录:在IDEA中必须将resources目录标记为资源根目录
- 服务配置文件:文件名必须是接口的全类名,内容是实现类的全类名
配置要点:
- 只选择需要打包的模块
- 指定主类作为入口(有main方法的类)
- "来自库的jar文件的配置"说明:
- 一定要选定到META-INF目录








