贝利信息

在Java中什么是SPI机制_Java服务发现原理解析

日期:2026-01-10 00:00 / 作者:P粉602998670
ServiceLoader 是 Java SPI 的官方实现,基于 META-INF/services/ 约定路径和 TCCL 加载接口实现类,本质是 JDK 内置的类路径扫描+文本解析+反射实例化流程。

ServiceLoader 是 Java SPI 机制的唯一官方入口,它不是“一种设计模式”,而是一套基于约定路径 + 线程上下文类加载器(TCCL)的硬编码发现逻辑


什么是 SPI?一句话说清本质

SPI 就是:你定义一个接口(比如 Logger),不写实现;别人在自己 jar 包里写实现类(比如 ConsoleLogger),并在 META-INF/services/com.example.Logger 文件里写上这行:

com.example.ConsoleLogger
。然后你用 ServiceLoader.load(Logger.class) 就能自动找到、实例化它——整个过程不写 new,不配 Spring,不改调用方代码。


为什么必须放 META-INF/services/?路径错一个字符就失效

因为 ServiceLoader 的源码里写死了这个路径逻辑:

private static final String PREFIX = "META-INF/services/";

它会拼出 META-INF/services/com.example.Logger,然后调用 classLoader.getResources(PREFIX + service.getName()) 去查所有匹配资源。一旦你写成 META-INF/service/(少个 s)、meta-inf/...(小写)、或放在 src/test/resources(测试路径未打进 jar),ServiceLoader 就完全看不到你的实现。


ServiceLoader 加载时到底发生了什么?

它不是“懒加载所有实现”,而是延迟迭代 + 即时反射

这意味着:如果你在 forEach 中抛异常,可能根本不知道是哪个实现崩了——建议用 try-catch 包住单次 next() 调用。


和 Spring 的 @SPI、Dubbo 的 ExtensionLoader 有啥区别?

Java 原生 ServiceLoader 是最简陋的版本:

Spring Boot 的 spring.factories 是对 SPI 的模仿升级:把配置从 META-INF/services/ 搬到 META-INF/spring.factories,并

SpringFactoriesLoader 解析,再交给 Spring 容器管理——所以你能用 @Autowired 注入,也能用 @Conditional 控制加载时机。

真正容易被忽略的是:原生 SPI 不做任何缓存。每次 load() 都重新扫描 classpath、重新解析文件、重新反射——高频调用场景(如日志门面)必须自己加单例缓存,否则性能雪崩。