Supplier 不参与 SPI 加载,而是封装 ServiceLoader 实现懒加载,避免重复扫描与构造开销,延迟实例化以提升启动性能、支持条件加载及统一异常处理。

Supplier 本身不参与 SPI 加载过程,它常被用作封装 ServiceLoader 的懒加载逻辑或延迟获取具体实现的桥梁。SPI 的核心加载机制由 ServiceLoader 承担,而 Supplier 是 Java 8 引入的函数式接口,作用是“按需提供一个结果”。在插件化场景中,它不替代 SPI,而是优化调用侧对 SPI 结果的使用方式——尤其是避免重复加载、控制实例化时机、解耦获取逻辑。
为什么不用 ServiceLoader 直接遍历,而要套一层 Supplier?
直接调用 ServiceLoader.load(X.class) 每次都会触发配置扫描和类加载(虽有缓存,但首次仍开销明显)。若插件实现类构造成本高、依赖复杂,或仅在特定条件才需启用,立即加载就不合理。Supplier 可把“加载+实例化”动作推迟到真正需要时执行:
- 避免启动阶段无谓初始化,提升主程序冷启动速度
- 支持按业务上下文选择性加载某一个实现(如根据配置名匹配)
- 便于统一做异常兜底、空值处理或日志埋点
- 与 Spring 等容器的 ObjectProvider / ObjectFactory 概念对齐,利于集成
常见组合写法:Supplier 包装单个首选实现
多数插件系统并不需要遍历所有实现,而是按策略选一个(如默认实现、最高优先级、或配置指定的)。此时可结合 findFirst() 和 Supplier:
private final Supplier<Logger> loggerSupplier = () -> {
return ServiceLoader.load(Logger.class)
.stream()
.map(ServiceLoader.Provider::get)
.filter(log -> "ConsoleLogger".equals(log.getClass().getSimpleName()))
.findFirst()
.orElseThrow(() -> new IllegalStateException("No ConsoleLogger found"));
};
调用方只需 loggerSupplier.get().log("msg"),加载逻辑完全隐藏。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
立即学习“Java免费学习笔记(深入)”;
Supplier 与 ServiceLoader 缓存配合使用
ServiceLoader 本身不自动缓存实例(只缓存已加载的类),每次 iterator() 都可能重新反射构造对象。若需单例语义,可用 Supplier + 静态 holder 或 AtomicReference 封装:
- 用
LazyOptional(如 Minecraft Modding 常见)或自定义线程安全 Supplier 实现一次加载多次复用 - Spring Boot 中可将 Supplier Bean 声明为
@Scope("singleton"),内部委托给 ServiceLoader - 注意:Supplier 不改变 SPI 的多实现本质,只是让调用更可控
实际项目中的典型位置
Supplier 很少出现在 SPI 配置或实现类中,它属于“消费者端”的包装层:
- 插件管理器(PluginManager)内部用 Supplier 缓存已解析的插件实例
- 框架抽象层(如日志门面、序列化器工厂)对外暴露 Supplier<T> 而非原始 ServiceLoader
- 测试 Mock 场景:用
Suppliers.ofInstance(mockLogger)快速替换真实 SPI 加载

















