ServiceLoader 通过读取 META-INF/services/ 下以接口全限定名命名的文本文件来加载实现类,文件每行一个实现类全限定名,需严格匹配包名和大小写;load() 仅解析配置,next() 才触发 Class.forName 和实例化,使用线程上下文类加载器,默认非单例。
ServiceLoader 不是“自动扫描类路径”,它只读 META-INF/services/ 下的配置文件,然后按需加载实现类——没配就找不到,配错名就 ClassNotFound。
它只看 META-INF/services/ 目录下以接口全限定名命名的文本文件(比如 java.sql.Driver 接口对应文件路径是 META-INF/services/java.sql.Driver)。该文件每行写一个实现类的全限定名,例如:
com.mysql.cj.jdbc.Driver org.postgresql.Driver
注意:文件名必须和 ServiceLoader.load(Xxx.class) 中传入的接口类型完全一致(包括包名),大小写敏感;文件内容不能有空格、注释或空行,否则解析失败或跳过。
Se 是懒加载的。调用
rviceLoaderServiceLoader.load(Interface.class) 只解析配置文件、缓存类名,不加载任何类。真正触发类加载和实例化的是迭代时调用 iterator().next() 或 forEach():
next() 会执行 Class.forName("com.example.MyImpl"),再调用其无参构造器next() 直接抛 ServiceConfigurationError
next() 都新建实例(不是单例)ServiceLoader 构造时默认使用 Thread.currentThread().getContextClassLoader(),不是 ServiceLoader.class.getClassLoader()。这意味着:
WEB-INF/lib,而 SPI 接口在容器共享类路径,可能因类加载器隔离导致找不到实现类ServiceLoader.load(Interface.class, myClassLoader)
module-info.java 中声明 provides Interface with Impl;,否则即使配置文件存在也会被忽略最常卡在这三处,错误现象通常是 NoSuchElementException(迭代为空)或 ServiceConfigurationError: Provider xxx not found:
META-INF/services/ 文件名是否与接口类全限定名一字不差(含包名)public MyImpl() {})jar -tf your.jar | grep services 确认文件确实被打进了 JAR 根路径SPI 机制本身极简,但它的脆弱性全藏在这些细节里:不报错、不警告、只是默默不返回任何实现。调试时别猜,先翻 JAR 包里的 META-INF/services/。