SPI通过配置文件解耦实现类,避免硬编码;需绕过双亲委派模型,依赖上下文类加载器;ServiceLoader.iterator()会全量实例化,非懒加载;spring.factories非标准SPI,是Spring自定义的按需加载机制。
当你的代码里写死 new MySQLDriver() 或 new LogbackLogger(),换数据库或日志框架就得改源码、重新编译——这在中间件、框架、SaaS 服务中完全不可接受。SPI 把“用哪个实现”这件事从代码里拎出来,交给配置文件和类路径决定:只要把新实现的 JAR 放进 classpath,并在 META-INF/services/com.example.PaymentService 里写上类名,ServiceLoader.load(PaymentService.class) 就能自动找到它。

JDK 核心类(比如 java.sql.DriverManager)由 Bootstrap 类加载器加载,而第三方驱动(如 com.mysql.cj.jdbc.Driver)通常在应用 classpath 下,由 AppClassLoader 加载。按双亲委派,Bootstrap 无法委托子加载器去加载应用类——所以 ServiceLoader 在初始化时会主动使用 Thread.currentThread().getContextClassLoader(),绕过默认委派链。这意味着:如果你在非主线程(比如线程池任务)里调用 ServiceLoader,且没显式设置上下文类加载器,就会加载失败或返回空迭代器。
ServiceLoader 的 iterator() 方法一调用就会遍历所有配置项、反射构造每个实现类——哪怕你只想要第一个匹配的。常见错误包括:
iterator(),可能触发重复加载(ServiceLoader 本身不是线程安全的)如果真要按需加载,得自己封装一层:读取 META-INF/services/xxx 文件内容,用 Class.forName(..., false, loader) 手动加载类,再用 clazz.getDeclaredConstructor().newInstance() 实例化——跳过 ServiceLoader 的自动机制。
spring.factories 文件位置也是 META-INF/spring.factories,格式是 org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx.AbcAutoConfiguration,但它不依赖 ServiceLoader,而是 Spring 自己解析并控制加载时机和条件(比如配合 @ConditionalOnClass)。关键区别在于:标准 SPI 是“发现即加载”,而 spring.factories 是“发现后按规则择优加载”。别误以为加了 spring.factories 就等于用了 Java SPI——它只是借了目录结构和配置风格,底层逻辑完全不同。
最常被忽略的一点:SPI 配置文件名必须是**完整接口类名**(含包路径),大小写敏感,不能有空格或 BOM;文件编码必须是 UTF-8 无签名;路径必须严格为 META-INF/services/xxx.xxx.Xxx ——少一个字母、多一个斜杠、用错类加载器,都会静默失败,且没有任何异常抛出,只会返回空 Iterator。