Java SPI 的核心实现依赖于 ClassLoader 机制,主要入口是 ServiceLoader 类。源码中定义了一个私有内部类 LazyIterator,它继承自 Iterator,负责封装具体的 SPI 加载逻辑。
从源码结构来看,核心的加载流程集中在两个关键方法上:hasNextService 和 nextService。
hasNextService 方法
该方法主要负责定位和解析配置文件。逻辑上可以分为几步:首先检查是否已有缓存的配置项,如果没有则通过 ClassLoader 加载资源。
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null) configs = ClassLoader.getSystemResources(fullName);
else configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
具体实现中,它会尝试读取 META-INF/services/xxx 文件,其中 xxx 对应接口的全路径名。SPI 模块需要在资源的 META-INF/services 目录下存放接口全路径名的文件,内容为具体的实现类名称。获取到文件后,会调用 parse 方法解析内容,提取所有实现类的名称列表。
nextService 方法
一旦确认有可用配置,nextService 负责实际创建实例。
private S nextService() {
if (!hasNextService()) throw ();
nextName;
nextName = ;
Class<?> c = ;
{
c = Class.forName(cn, , loader);
} (ClassNotFoundException x) {
fail(service, + cn + );
}
(!service.isAssignableFrom(c)) {
fail(service, + cn + );
}
{
service.cast(c.newInstance());
providers.put(cn, p);
p;
} (Throwable x) {
fail(service, + cn + , x);
}
();
}

