前提 紧接着上一篇《通过源码浅析JDK中的资源加载》,ServiceLoader是SPI(Service Provider Interface)中的服务类加载的核心类,也就是,这篇文章先介绍ServiceLoader的使用方式,再分析它的源码。
ServiceLoader的使用 这里先列举一个经典的例子,MySQL的Java驱动就是通过ServiceLoader加载的,先引入mysql-connector-java
的依赖:
<dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.47</version > </dependency >
查看这个依赖的源码包下的META-INF目录,可见:
我们接着查看java.lang.DriverManager,静态代码块里面有:
static { loadInitialDrivers(); println("JDBC DriverManager initialized" ); }
其中,可以查看loadInitialDrivers()
有如下的代码片段:
java.lang.DriverManager是启动类加载器加载的基础类,但是它可以加载rt.jar
包之外的类,上篇文章提到,这里打破了双亲委派模型,原因是:ServiceLoader中使用了线程上下文类加载器去加载类。这里JDBC加载的过程就是典型的SPI的使用,总结规律如下:
1、需要定义一个接口。
2、接口提供商需要实现第1步中的接口。
3、接口提供商在META-INF/services目录下建立一个文本文件,文件名是第1步中定义的接口的全限定类名,文本内容是接口的实现类的全限定类名,每个不同的实现占独立的一行。
4、使用ServiceLoader加载接口类,获取接口的实现的实例迭代器。
举个简单的实例,先定义一个接口和两个实现:
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函数验证:
基于SPI或者说ServiceLoader加载接口实现这种方式也可以广泛使用在相对基础的组件中,因为这是一个成熟的规范。
ServiceLoader源码分析 上面通过一个经典例子和一个实例介绍了ServiceLoader的使用方式,接着我们深入分析ServiceLoader的源码。我们先看ServiceLoader的类签名和属性定义:
public final class ServiceLoader <S > implements Iterable <S > { private static final String PREFIX = "META-INF/services/" ; private final Class<S> service; private final ClassLoader loader; private final AccessControlContext acc; private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); private LazyIterator lookupIterator; }
ServiceLoader实现了Iterable接口,这一点提示了等下我们在分析它源码的时候,需要重点分析iterator()
方法的实现。ServiceLoader依赖于类加载器实例进行类加载,它的核心属性LazyIterator是就是用来实现iterator()
方法的,下文再重点分析。接着,我们分析ServiceLoader的构造函数:
public void reload () { providers.clear(); 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必须依赖于它的静态方法调用私有构造去完成实例化操作,而实例化过程主要做了几步:
1、判断传入的接口或者类的Class实例不能为null,否则会抛出异常。
2、如果传入的ClassLoader实例为null,则使用应用类加载器(Application ClassLoader)。
3、实例化访问控制上下文。
4、调用实例方法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 () { 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(); } }; }
iterator()
内部仅仅是Iterator接口的匿名实现,hasNext()
和next()
方法都是优先判断缓存中是否已经存在实现类的实例,如果存在则直接从缓存中返回,否则调用懒加载迭代器LazyIterator的实例去获取,而LazyIterator本身也是一个Iterator接口的实现,它是ServiceLoader的一个私有内部类,源码如下:
private class LazyIteratorimplements 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 () { 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 ; } 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(); } 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); } } return names.iterator(); } private int parseLine (Class<?> service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError { 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 ) { if ((ln.indexOf(' ' ) >= 0 ) || (ln.indexOf('\t' ) >= 0 )) fail(service, u, lc, "Illegal configuration-file syntax" ); int cp = ln.codePointAt(0 ); if (!Character.isJavaIdentifierStart(cp)) fail(service, u, lc, "Illegal provider-class name: " + ln); 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)