看了上篇spi使用后,你或许觉得spi太好用了吧,但或许也有疑问:
- 为什么只能放在
META-INF/services/
目录下?为什么要用全路径命名? - 他的实现原理是什么?
基于这两个问题,我们深入探究下ServiceLoader
源码。
构造函数
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
// Android-changed: Do not use legacy security code.
// On Android, System.getSecurityManager() is always null.
// acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
- service 就是普通的
class
类 - loader
ClassLoader
类型变量,如果传空就默认ClassLoader.getSystemClassLoader
可以看到构造函数里调用了reload()
方法,且Android的 ClassLoader
类没有使用AccessController
reload()
方法
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
初始化了两个变量providers
是用来缓存class的,lookupIterator
是我们获取子类继承的核心处理类了,而 ClassLoader
本身继承了Iterable
在iterator()
方法里调用lookupIterator
来实现重写方法,ServiceLoader
的操作都是通过该变量来实现的
iterator()
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
//首先检查缓存,缓存没有则从lookupIterator读取
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
//首先检查缓存,缓存没有则从lookupIterator读取
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
LazyIterator
LazyIterator
是Iterator
的继承类,其两个实现方法hasNext()
和next()
分别调用了hasNextService()
和nextService()
所以我们只要看这两个方法就可以了
hasNextService()
private boolean hasNextService() {
//下一个继承类的名字,不为空则直接返回true
if (nextName != null) {
return true;
}
//初始化配置
if (configs == null) {
try {
//PREFIX = "META-INF/services/";
//路径全名称为: "META-INF/services/" + 类的名称
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/
目录下配置的接口全路径名的文件,读取里面的继承类文件名,来判断当前节点是否还有继承类
这里就可以看到了我们的第一个问题:
- 为什么只能放在
META-INF/services/
目录下?通过PREFIX
变量我们可以看到了原因,该变量定义了路径的位置 - 为什么要用全路径命名?因为
service.getName()
获取的就是全路径名
nextService()
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
// Android-changed: Let the ServiceConfigurationError have a cause.
"Provider " + cn + " not found", x);
// "Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
// Android-changed: Let the ServiceConfigurationError have a cause.
ClassCastException cce = new ClassCastException(
service.getCanonicalName() + " is not assignable from " + c.getCanonicalName());
fail(service,
"Provider " + cn + " not a subtype", cce);
// fail(service,
// "Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
- 该方法首先调用
hasNextService()
获取当前节点的继承子类名称,如果没有会抛出异常 - 然后通过
Class.forName
实例化该类 - 然后通过
isAssignableFrom
校验获得到的类是否是service
的子类 - 最后通过
cast
强类型转换为service
类型,并添加到providers
缓存里返回
整个LazyIterator
的实现就介绍完了
这里就解释了第二个问题ServiceLoader
实现原理?他通过LazyIterator
类获取META-INF/services/
目录下接口对应的文件,并读取里面的继承类名,然后通过类实例化返回,最终我们就可以获取到了接口对应实现的子类。