Java SPI 是一种轻量解耦机制,用于将业务变量逻辑从主流程抽离,通过定义接口、实现类注册到 META-INF/services/ 下,运行时由 ServiceLoader 加载,实现扩展点开放与核心逻辑稳定。

Java SPI 是一种轻量但有力的解耦机制,特别适合把“业务变量”从主流程中抽离出来,让核心逻辑稳定、扩展点开放。它不依赖 Spring 容器,也不需要复杂配置,只要接口定义清晰、实现类按约定注册,就能在运行时自动识别并加载。
明确扩展点:定义可插拔的业务变量接口
所谓“业务变量”,指的是那些随场景变化、但又不该硬编码进主流程的值或行为——比如不同渠道的订单来源标识、多租户的计费策略、地区化的价格计算规则等。关键不是存数据,而是封装“怎么取、怎么算”的逻辑。
例如定义一个通用的业务上下文变量提供器:
public interface BusinessVariableProvider {
String variableKey(); // 变量唯一标识,如 "order_source", "tenant_pricing_rule"
Object resolve(BusinessContext context); // 根据上下文动态计算/获取值
int order(); // 加载优先级,用于多实现排序
}
这个接口本身不带业务细节,只声明契约。所有具体实现都面向它编程,主流程只依赖这个接口,不关心谁提供、怎么提供。
编写实现类并注册:让变量“自己报到”
每个业务方写自己的实现,比如电商渠道组实现订单来源:
@Component
public class ChannelSourceProvider implements BusinessVariableProvider {
@Override
public String variableKey() {
return "order_source";
}
@Override
public Object resolve(BusinessContext context) {
return context.getHeaders().get("x-channel-id");
}
@Override
public int order() {
return 10;
}
}
然后在 META-INF/services/com.example.spi.BusinessVariableProvider 文件中写入:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
com.example.channel.ChannelSourceProvider com.example.tenant.TenantPricingProvider com.example.region.RegionPriceProvider
注意三点:
- 文件路径必须严格为
META-INF/services/接口全限定名 - 每行一个实现类全名,不能有空格或注释
- 实现类需有无参构造方法(否则 ServiceLoader 实例化失败)
运行时加载与使用:按需获取,不侵入主流程
在核心服务中,用标准 JDK 方式加载:
ServiceLoader<businessvariableprovider> loader =
ServiceLoader.load(BusinessVariableProvider.class);
Map<string businessvariableprovider> providers = new LinkedHashMap();
loader.forEach(p -> providers.put(p.variableKey(), p));
// 后续可通过 providers.get("order_source").resolve(context) 获取值
</string></businessvariableprovider>
也可以封装成工具类,支持按 key 查找、按 order 排序、缓存首次加载结果。主业务代码完全不感知实现来源,只通过 key 请求变量,真正实现“调用方不依赖实现方”。
与 Spring 集成:保持轻量,不破坏 SPI 原意
如果项目已用 Spring,不必强行把 SPI 实现变成 @Bean。推荐做法是:
- 在 Spring Boot 启动阶段(如 ApplicationRunner)一次性加载所有 SPI 实现,并注册为 Spring 管理的 Bean(可选)
- 或直接将
ServiceLoader结果放入 ApplicationContext,供其他 Bean 注入使用 - 避免用 @Component + @Autowired 替代 SPI —— 那就退化成普通 IOC,失去“外部插件可热替换”的能力
SPI 的价值在于模块边界清晰:业务 jar 包只需包含接口定义和自身实现,扔进 classpath 就生效,无需改主工程任何一行代码。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










