紧接着上一篇《经过源码浅析JDK中的资源加载》,ServiceLoader是SPI(Service Provider Interface)中的服务类加载的核心类,也就是,这篇文章先介绍ServiceLoader的使用方式,再分析它的源码。java
这里先列举一个经典的例子,MySQL的Java驱动就是经过ServiceLoader加载的,先引入mysql-connector-java
的依赖:mysql
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>
查看这个依赖的源码包下的META-INF目录,可见:sql
咱们接着查看java.lang.DriverManager,静态代码块里面有:缓存
static { loadInitialDrivers(); println("JDBC DriverManager initialized"); }
其中,能够查看loadInitialDrivers()
有以下的代码片断:app
java.lang.DriverManager是启动类加载器加载的基础类,可是它能够加载rt.jar
包以外的类,上篇文章提到,这里打破了双亲委派模型,缘由是:ServiceLoader中使用了线程上下文类加载器去加载类。这里JDBC加载的过程就是典型的SPI的使用,总结规律以下:ide
举个简单的实例,先定义一个接口和两个实现:函数
public interface Say { void say(); } public class SayBye implements Say { @Override public void say() { System.out.println("Bye!"); } } public class SayHello implements Say { @Override public void say() { System.out.println("Hello!"); } }
接着在项目的META-INF/services中添加文件以下:源码分析
最后经过main函数验证:ui
基于SPI或者说ServiceLoader加载接口实现这种方式也能够普遍使用在相对基础的组件中,由于这是一个成熟的规范。this
上面经过一个经典例子和一个实例介绍了ServiceLoader的使用方式,接着咱们深刻分析ServiceLoader的源码。咱们先看ServiceLoader的类签名和属性定义:
public final class ServiceLoader<S> implements Iterable<S>{ //须要加载的资源的路径的目录,固定是ClassPath下的META-INF/services/ private static final String PREFIX = "META-INF/services/"; // ServiceLoader须要正在须要加载的类或者接口 // The class or interface representing the service being loaded private final Class<S> service; // ServiceLoader进行类加载的时候使用的类加载器引用 // The class loader used to locate, load, and instantiate providers private final ClassLoader loader; // 权限控制上下文 // The access control context taken when the ServiceLoader is created private final AccessControlContext acc; //基于实例的顺序缓存类的实现实例,其中Key为实现类的全限定类名 // Cached providers, in instantiation order private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // 当前的"懒查找"迭代器,这个是ServiceLoader的核心 // The current lazy-lookup iterator private LazyIterator lookupIterator; //暂时忽略其余代码... }
ServiceLoader实现了Iterable接口,这一点提示了等下咱们在分析它源码的时候,须要重点分析iterator()
方法的实现。ServiceLoader依赖于类加载器实例进行类加载,它的核心属性LazyIterator是就是用来实现iterator()
方法的,下文再重点分析。接着,咱们分析ServiceLoader的构造函数:
public void reload() { //清空缓存 providers.clear(); //构造LazyIterator实例 lookupIterator = new LazyIterator(service, loader); } 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(); }
ServiceLoader只有一个私有的构造函数,也就是它不能经过构造函数实例化,可是要实例化ServiceLoader必须依赖于它的静态方法调用私有构造去完成实例化操做,而实例化过程主要作了几步:
reload()
,清空目标加载类的实现类实例的缓存而且构造LazyIterator实例。注意一点是实例方法reload()
的修饰符是public,也就是能够主动调用去清空目标加载类的实现类实例的缓存和从新构造LazyIterator实例。接着看ServiceLoader提供的静态方法:
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){ return new ServiceLoader<>(service, loader); } public static <S> ServiceLoader<S> load(Class<S> service) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } public static <S> ServiceLoader<S> loadInstalled(Class<S> service) { ClassLoader cl = ClassLoader.getSystemClassLoader(); ClassLoader prev = null; while (cl != null) { prev = cl; cl = cl.getParent(); } return ServiceLoader.load(service, prev); }
上面的三个公共静态方法都是用于构造ServiceLoader实例,其中load(Class<S> service, ClassLoader loader)
就是典型的静态工厂方法,直接调用ServiceLoader的私有构造器进行实例化,除了须要指定加载类的目标类型,还须要传入类加载器的实例。load(Class<S> service)
实际上也是委托到load(Class<S> service, ClassLoader loader)
,不过它使用的类加载器指定为线程上下文类加载器,通常状况下,线程上下文类加载器获取到的就是应用类加载器(系统类加载器)。loadInstalled(Class<S> service)
方法又看出了"双亲委派模型"的影子,它指定类加载器为最顶层的启动类加载器,最后也是委托到load(Class<S> service, ClassLoader loader)
。接着咱们须要重点分析ServiceLoader#iterator()
:
public Iterator<S> iterator() { //Iterator的匿名实现 return new Iterator<S>() { //目标类实现类实例缓存的Map的Entry的迭代器实例 Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); //先从缓存中判断是否有下一个实例,不然经过懒加载迭代器LazyIterator去判断是否存在下一个实例 public boolean hasNext() { if (knownProviders.hasNext()) return true; return lookupIterator.hasNext(); } //若是缓存中判断是否有下一个实例,若是有则从缓存中的值直接返回 //不然经过懒加载迭代器LazyIterator获取下一个实例 public S next() { if (knownProviders.hasNext()) return knownProviders.next().getValue(); return lookupIterator.next(); } //不支持移除操做,直接抛异常 public void remove() { throw new UnsupportedOperationException(); } }; }
iterator()
内部仅仅是Iterator接口的匿名实现,hasNext()
和next()
方法都是优先判断缓存中是否已经存在实现类的实例,若是存在则直接从缓存中返回,不然调用懒加载迭代器LazyIterator的实例去获取,而LazyIterator自己也是一个Iterator接口的实现,它是ServiceLoader的一个私有内部类,源码以下:
private class LazyIteratorimplements Iterator<S>{ Class<S> service; ClassLoader loader; //加载的资源的URL集合 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() { //若是下一个须要加载的实现类的全限定类名不为null,则说明资源中存在内容 if (nextName != null) { return true; } //若是加载的资源的URL集合为null则尝试进行加载 if (configs == null) { try { //资源的名称,META-INF/services + '须要加载的类的全限定类名' //这样获得的恰好是须要加载的文件的资源名称 String fullName = PREFIX + service.getName(); //这里其实ClassLoader实例应该不会为null if (loader == null) configs = ClassLoader.getSystemResources(fullName); else //从ClassPath加载资源 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; } private S nextService() { if (!hasNextService()) throw new NoSuchElementException(); String cn = nextName; nextName = null; Class<?> c = null; try { //反射构造Class<S>实例 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 { //经过Class#newInstance()进行实例化,而且强制转化为对应的类型的实例 S p = service.cast(c.newInstance()); //添加缓存,Key为实现类的全限定类名,Value为实现类的实例 providers.put(cn, p); return p; } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated", x); } throw new Error(); // This cannot happen } 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(); } }
LazyIterator
也是Iterator接口的实现,它的Lazy特性代表它老是在ServiceLoader的Iterator接口匿名实现iterator()
执行hasNext()
判断是否有下一个实现或者next()
获取下一个实现类的实例的时候才会"懒判断"或者"懒加载"下一个实现类的实例。最后是加载资源文件后对资源文件的解析过程的源码:
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); } } //返回的是ArrayList的迭代器实例 return names.iterator(); } //解析资源文件中每一行的内容 private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names)throws IOException, ServiceConfigurationError{ // 下一行没有内容,返回-1,便于上层能够跳出循环 String ln = r.readLine(); if (ln == null) { return -1; } //若是存在'#'字符,截取第一个'#'字符串以前的内容,'#'字符以后的属于注释内容 int ci = ln.indexOf('#'); if (ci >= 0) ln = ln.substring(0, ci); ln = ln.trim(); int n = ln.length(); if (n != 0) { //不能存在空格字符' '和特殊字符'\t' if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) fail(service, u, lc, "Illegal configuration-file syntax"); int cp = ln.codePointAt(0); //判断第一个char是否一个合法的Java起始标识符 if (!Character.isJavaIdentifierStart(cp)) fail(service, u, lc, "Illegal provider-class name: " + ln); //判断全部其余字符串是否属于合法的Java标识符 for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { cp = ln.codePointAt(i); if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) fail(service, u, lc, "Illegal provider-class name: " + ln); } //若是缓存中不存在加载出来的全类名或者已经加载的列表中不存在加载出来的全类名则添加进去加载的全类名列表中 if (!providers.containsKey(ln) && !names.contains(ln)) names.add(ln); } return lc + 1; }
整个资源文件的解析过程并不复杂,主要包括文件内容的字符合法性判断和缓存避免重复加载的判断。
SPI被普遍使用在第三方插件式类库的加载,最多见的如JDBC、JNDI、JCE(Java加密模块扩展)等类库。理解ServiceLoader的工做原理有助于编写扩展性良好的可插拔的类库。
(本文完 c-1-d e-20181014)