ServiceLoader 的核心价值在于实现接口与实现的硬性解耦:业务代码仅依赖接口,运行时通过 META-INF/services 配置动态加载实现类,无需 import、new 或 Spring 扫描,且支持多实现并存与容错加载。

ServiceLoader 的核心价值,就是让业务逻辑代码完全不依赖具体实现类——只面向接口编程,运行时才决定用哪个实现。这种解耦不是靠抽象或设计模式模拟出来的,而是由 JVM 原生机制保障的硬性隔离。
接口定义与实现分离
业务模块只需声明一个接口(如 PaymentProcessor),所有支付逻辑都围绕这个接口展开。调用方代码里不会出现 new AlipayProcessor() 或 import com.xxx.AlipayProcessor 这类强引用。整个编译期,业务模块的 classpath 里甚至可以没有任何一个实现类。
- 接口必须是 public、有明确方法签名,最好独立发布为 SPI artifact(如
payment-spi.jar) - 每个实现类(如
AlipayProcessor、WechatProcessor)放在各自模块中,不与业务模块混编 - 实现类必须提供 public 无参构造器,否则 ServiceLoader 反射失败
META-INF/services 是解耦的“注册中心”
实现类不靠 import、不靠 Spring 扫描、不靠手动 new,而是通过标准资源路径被发现:
- 文件必须放在
src/main/resources/META-INF/services/com.example.PaymentProcessor(注意:是点号分隔,不是斜杠) - 文件内容只写一行:
com.alipay.AlipayProcessor,末尾不能有空格或 BOM - 该文件随实现模块打包进 jar,部署时只要 jar 在 classpath 中,ServiceLoader 就能读到
业务代码只操作接口,不感知实现细节
业务逻辑中调用方式极简,且稳定不变:
ServiceLoader<PaymentProcessor> loader = ServiceLoader.load(PaymentProcessor.class);
for (PaymentProcessor p : loader) {
if (p.supports(channel)) {
return p.process(order);
}
}
- 无需修改业务代码,就能新增一种支付渠道——只要加个新实现模块 + 对应配置文件
- 可同时存在多个实现,按需选择(比如按 channel、环境变量、配置开关等判断)
- 如果某个实现类缺失或加载失败,ServiceLoader 会跳过它,不影响其他实现正常工作
注意与 Spring 等容器的边界
ServiceLoader 加载的实例是纯 JVM 实例,不经过 Spring 容器管理:
- @Autowired 字段为 null,@PostConstruct 不执行,事务代理无效
- 若需 Spring 能力,应把 ServiceLoader 当作“发现器”,再用
applicationContext.getBean(implClass)获取托管 Bean - 更推荐做法:实现类本身加
@Component,由 Spring 扫描注册;ServiceLoader 仅用于跨模块发现类名,不直接实例化

















