ServiceLoader源代码分析
1.ServiceLoader可以用来做什么
我比较喜欢用通俗的语言来说明,ServiceLoader我用通俗的语言来讲的话,就是可以通过配置文件灵活的来指定一个接口的实现类,再用ClassLoader来把这些实现类加载到内存中。这么做的好处是什么,这么做的好处是什么?显而易见,使得我们写的程序更加灵活,更加容易扩展。ServiceLoader是Java的SPI实现。
2.直接上个sample
定义一个接口:
两个实现类:
配置文件:
配置文件一定要放到META-INF/service 文件夹下面。并且文件名字必须是接口的包名加上类名(接口的全类名)
通过ServiceLoader来加载指定的实现类:
3.运行效果
我们可以把配置文件里面的com.smallcode.service.WxPayService删掉,执行的结果如下:
只剩下支付宝支付的字样了。
可以看出已经通过ServiceLoader把指定的实现类加载进去了。
3.ServiceLoader核心源代码分析
以上简单的演示下了ServiceLoader的效果,下面看下ServiceLoader核心源代码,来具体看下ServiceLoader是如何实现的。
沿着main方法里面的ServiceLoader.load方法追踪源代码:
// ServiceLoader.java
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
该方法调用了load(Class<S> service,ClassLoader loader)
方法:
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
调用了构造函数 ServiceLoader(Class<S> svc, ClassLoader cl)
:
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;
reload();
}
调用了reload()
方法:
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
追踪到一个LazyIterator,一个迭代器。
我们再看ServiceLoader 实现了 Iterable接口
public final class ServiceLoader<S>
implements Iterable<S>
当我们的在main方法里面通过foreach的时候,会调用ServiceLoader类里面的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(); // <1>
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
这里涉及到迭代器原理和foreach,在这里不做详细介绍。
接着往下追踪代码:
拼接配置文件路径,可以看出路径是META-INF/services/接口全类名。
根据获取的路径读取文件,返回一个集合,配置文件每行对应集合一条数据。
private Iterator<String> parse(Class<?> service, URL u)
throws ServiceConfigurationError
{
InputStream in = null;
BufferedReader r = null;
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
try {
if (r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
return names.iterator();
}
在nextService(迭代器中next方法调用了nextService)可以看出,通过ClassLoader加载刚刚从配置文件里面获取到实现类。然后返回该实现类。
4.总结
本文首先简单的介绍了ServiceLoader,在编写了一个实例,最后通过源代码分析ServicerLoader具体的实现。总体来说ServiceLoader实现比较简单。