serviceloader 是 jdk 原生插件发现机制,适用于接口明确、实现稳定且无需 spring 生命周期管理的场景;需正确定义接口、配置 meta-inf/services 文件,并结合 spring 容器使用以兼顾发现能力与依赖注入。

ServiceLoader 是 JDK 原生支持的轻量级插件发现机制,适合接口契约明确、实现类稳定、无需 Spring 生命周期管理的场景。它不等于“热插拔”,但能实现编译解耦 + 运行时动态发现,是构建可插拔架构的基石之一。
定义清晰的服务接口与扩展点
接口是插件系统的契约核心,应满足:单一职责、参数尽量用 POJO、避免框架强依赖(如不直接传 HttpServletRequest 或 ApplicationContext)。例如:
- 定义 PaymentProcessor 接口,只声明
supports(String channel)和execute(Order order) - 不暴露 Spring 注解、不依赖特定日志实现、不返回 CompletableFuture(除非所有插件都统一约定)
- 可加
@FunctionalInterface或提供默认实现类(如DefaultPaymentProcessor),降低接入门槛
正确配置 META-INF/services 文件
这是 ServiceLoader 能否加载到实现类的决定性环节,90% 的失败源于此:
- 文件必须放在 classpath 根路径下,即
META-INF/services/com.example.PaymentProcessor(不是META-INF/services/com/example/PaymentProcessor) - 文件名必须是接口的全限定名,大小写、包名、点号一个都不能错
- 文件内容为实现类全限定名,每行一个,末尾不能有空格、BOM、空行
- Maven 构建时确认该资源被包含进 jar:解压 jar 查看
META-INF/services/是否存在且可读
用 ServiceLoader 发现,用 Spring 管理(推荐组合用法)
ServiceLoader 本身不走 Spring 容器,直接调用 newInstance() 会导致 @Autowired 为空、@PostConstruct 不触发、无 AOP。正确做法是:
- 先用
ServiceLoader.load(PaymentProcessor.class)获取所有实现类的类名字符串(或 Class 对象) - 再通过
applicationContext.getBean(className, PaymentProcessor.class)拿到已托管的 Bean 实例 - 前提是这些实现类本身已被 Spring 扫描到(如标注
@Component或在@Configuration中注册) - 这样既享受 SPI 的发现能力,又保留 Spring 的依赖注入、代理、生命周期等全部能力
处理多实现共存与策略控制
ServiceLoader 遍历顺序由 classpath 中 jar 加载顺序决定,不可靠。不要写 serviceIterator.next() 当默认实现:
- 让每个实现类提供
int getOrder()方法,主逻辑中收集全部实例后按序排序 - 启用开关建议用配置驱动:例如在实现类里读取
plugin.enabled.alipay=true,或解析插件元数据META-INF/plugin.yml - 避免用类名、包名做判断逻辑(如
clazz.getName().contains("Alipay")),这会破坏抽象性
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











