ServiceLoader
首先讲下常见用法:
//spi接口
public interface IHello {
void sayHello();
}
//接口实现
public class HelloImpl1 implements IHello {
@Override
public void sayHello() {
System.out.println("hello world1");
}
}
public class HelloImpl2 implements IHello {
@Override
public void sayHello() {
System.out.println("hello world2");
}
}
public static void main(String[] args){
ServiceLoader<IHello> s = ServiceLoader.load(IHello.class);
Iterator<IHello> iHelloIterator = s.iterator();
while (iHelloIterator.hasNext()) {
IHello iHello = iHelloIterator.next();
iHello.sayHello();
}
}
源码分析
构造函数
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null //是否启动了SecurityManager,这里会决定后面是否用特权doPrivileged方法
reload();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
ServiceLoader的构造函数很简单,唯一可能有疑问的点是这个acc。本文主要介绍spi,所以简单提一下这个acc,这个是java的安全管理机制,当启动了SecurityManager的时候,会去检查权限(默认不启动,拥有所有权限)。
接下来是他的iterator
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
knownProviders用来遍历providers的迭代器,而providers缓存了之前生成的实例,防止多次遍历是重复生成对象。
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
如果knownProviders.hasNext()为true,表示缓存中存在,直接取出即可。缓存不存在时用了lookupIterator.hasNext()
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
这个LazyIterator是个inner class
private class LazyIterator implements Iterator<S>{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
private boolean hasNextService() {
......
}
private S nextService() {
......
}
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction<S> action = new PrivilegedAction<S>() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
hasNext和next方法很简单,根据是否启动了SecurityManager来决定是否要用AccessController.doPrivileged来调用hasNextService和nextService。重点来看一下hasNextService和nextService方法。
hasNextService
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/路径下,以spi名称命名的文件中的实现类的完整类名。
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,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
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
}
这一步根据之前得到的类名,加载类并反射生成对象,然后将生成好的对象放到providers的缓存中。这样完成了spi接口实现类的实例化,下一篇文章我们来看看dubbo中的spi是怎么做的。