源码级深度理解 Java SPI( 二 )

3.2 ServiceLoader 的工作流程(1)ServiceLoader.load 静态方法
应用程序加载 Java SPI 服务,都是先调用 ServiceLoader.load 静态方法 。
ServiceLoader.load 静态方法的作用是:
① 指定类加载 ClassLoader 和访问控制上下文;
【源码级深度理解 Java SPI】② 然后 , 重新加载 SPI 服务

  • 清空缓存中所有已实例化的 SPI 服务
  • 根据 ClassLoader 和 SPI 类型,创建懒加载迭代器
这里 , 摘录 ServiceLoader.load 相关源码,如下:
// service 传入的是期望加载的 SPI 接口类型// loader 是用于加载 SPI 服务的类加载器public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {return new ServiceLoader<>(service, loader);}public void reload() {// 清空缓存中所有已实例化的 SPI 服务providers.clear();// 根据 ClassLoader 和 SPI 类型,创建懒加载迭代器lookupIterator = new LazyIterator(service, loader);}// 私有构造方法// 重新加载 SPI 服务private ServiceLoader(Class<S> svc, ClassLoader cl) {service = Objects.requireNonNull(svc, "Service interface cannot be null");// 指定类加载 ClassLoader 和访问控制上下文loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;// 然后,重新加载 SPI 服务reload();}(2)应用程序通过 ServiceLoader 的 iterator 方法遍历 SPI 实例
ServiceLoader 的类定义,明确了 ServiceLoader 类实现了 Iterable<T> 接口 , 所以 , 它是可以迭代遍历的 。实际上,ServiceLoader 类维护了一个缓存 providers( LinkedHashMap 对象),缓存 providers 中保存了已经被成功加载的 SPI 实例,这个 Map 的 key 是 SPI 接口实现类的全限定名 , value 是该实现类的一个实例对象 。
当应用程序调用 ServiceLoader 的 iterator 方法时,ServiceLoader 会先判断缓存 providers 中是否有数据:如果有,则直接返回缓存 providers 的迭代器;如果没有,则返回懒加载迭代器的迭代器 。
public Iterator<S> iterator() {return new Iterator<S>() {// 缓存 SPI providersIterator<Map.Entry<String,S>> knownProviders= providers.entrySet().iterator();// lookupIterator 是 LazyIterator 实例,用于懒加载 SPI 实例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();}};}(3)懒加载迭代器的工作流程
上面的源码中提到了,lookupIterator 是 LazyIterator 实例,而 LazyIterator 用于懒加载 SPI 实例 。那么,LazyIterator 是如何工作的呢?
这里,摘取 LazyIterator 关键代码
hasNextService 方法:
  • 拼接 META-INF/services/ + SPI 接口全限定名
  • 通过类加载器 , 尝试加载资源文件
  • 解析资源文件中的内容,获取 SPI 接口的实现类的全限定名 nextName
nextService 方法:
  • hasNextService() 方法解析出了 SPI 实现类的的全限定名 nextName,通过反射,获取 SPI 实现类的类定义 Class 。
  • 然后,尝试通过 Class 的 newInstance 方法实例化一个 SPI 服务对象 。如果成功 , 则将这个对象加入到缓存 providers 中并返回该对象 。
private boolean hasNextService() {if (nextName != null) {return true;}if (configs == null) {try {// 1.拼接 META-INF/services/ + SPI 接口全限定名// 2.通过类加载器,尝试加载资源文件// 3.解析资源文件中的内容String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = 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;}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 s");}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}3.3 SPI 和类加载器通过上面两个章节中,走读 ServiceLoader 代码,我们已经大致了解 Java SPI 的工作原理,即通过 ClassLoader 加载 SPI 配置文件,解析 SPI 服务 , 然后通过反射,实例化 SPI 服务实例 。我们不妨思考一下 , 为什么加载 SPI 服务时 , 需要指定类加载器 ClassLoader 呢?

推荐阅读