ServiceLoader是jdk6里面引进的一个特性。它用来实现SPI(Service Provider Interface),一种服务发现机制,很多框架用它来做来做服务的扩展发现。
系统里抽象的各个模块一般会有很多种不同的实现,如JDBC、日志等。通常模块之间我们均是基于接口进行编程,而不是对实现类进行硬编码。这时候就需要一种动态替换发现的机制,即在运行时动态地给接口添加实现,而不需要在程序中指明。
引用自JDK文档对于java.util.ServiceLoader的描述:
服务是一个熟知的接口和类(通常为抽象类)集合。服务提供者是服务的特定实现。提供者中的类通常实现接口,并子类化在服务本身中定义的子类。服务提供者可以以扩展的形式安装在 Java 平台的实现中,也就是将 jar 文件放入任意常用的扩展目录中。也可通过将提供者加入应用程序类路径,或者通过其他某些特定于平台的方式使其可用。
为了加载,服务由单个类型表示,也就是单个接口或抽象类。一个给定服务的提供者包含一个或多个具体类,这些类扩展了此服务类型,具有特定于提供者的数据和代码。提供者类通常不是整个提供者本身而是一个代理,它包含足够的信息来决定提供者是否能满足特定请求,还包含可以根据需要创建实际提供者的代码。提供者类的详细信息高度特定于服务;任何单个类或接口都不能统一它们,因此这里没有定义任何这种类型。此设施唯一强制要求的是,提供者类必须具有不带参数的构造方法,以便它们可以在加载中被实例化。
当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/ 目录里同时创建一个以服务接口命名的该服务接口具体实现类文件。当外部程序装配该模块时,通过该jar包META-INF/services/里的配置文件找到具体的实现类名,从而完成模块的注入,而不需要在代码里定制。
在JDBC中使用了ServiceLoader对不同数据库驱动进行加载。
DriverManager通过ServiceLoader加载数据库驱动,JDBC原理可移步【JDBC原理】。
ServiceLoaderServiceLoader 使用小栗子loadedDrivers = ServiceLoader.load(Driver.class); Iterator driversIterator = loadedDrivers.iterator();
首先定义一个SPIService接口
package com.study;
public interface SPIService {
void excute();
}
再定义两个实现类
package com.study.impl;
import com.study.SPIService;
public class SPIServiceImpl1 implements SPIService {
@Override
public void excute() {
System.out.println("SPIServiceImpl1");
}
}
package com.study.impl;
import com.study.SPIService;
public class SPIServiceImpl2 implements SPIService {
@Override
public void excute() {
System.out.println("SPIServiceImpl2");
}
}
在ClassPath路径下配置添加文件,META-INF/services/com.study.SPIService,文件名为接口的全限定类名。在配置文件中加入两个实现类的全限定类名。
com.study.impl.SPIServiceImpl1 com.study.impl.SPIServiceImpl2
写一个测试类SPITest.java
public class SPITest {
public static void main(String[] args) {
ServiceLoader loaders = ServiceLoader.load(SPIService.class);
Iterator it = loaders.iterator();
while (it.hasNext()) {
SPIService spiSer= it.next();
spiSer.excute();
}
}
}
运行结果:
SPIServiceImpl1
SPIServiceImpl2
ServiceLoader的load方法将在META-INF/services/com.study.SPIService中配置的子类都进行了加载。
ServiceLoader的核心源码分析public final class ServiceLoaderimplements Iterable{ // 需要加载的资源的配置文件路径 private static final String PREFIX = "META-INF/services/"; // 加载的服务类或接口 private Classservice; // 类加载时用到的类加载器 private ClassLoader loader; // 基于实例的已加载的顺序缓存类,其中Key为实现类的全限定类名 private LinkedHashMapproviders = new LinkedHashMap<>(); // "懒查找"迭代器,ServiceLoader的核心 private LazyIterator lookupIterator; public void reload() { // 清空缓存 providers.clear(); // 构造LazyIterator实例 lookupIterator = new LazyIterator(service, loader); } // 私有构造方法 private ServiceLoader(Class svc, ClassLoader cl) { service = svc; loader = cl; reload(); } }
ServiceLoader只有一个私有的构造函数,也就是它不能通过构造函数实例化,但是要实例化ServiceLoader必须依赖于它的静态方法调用私有构造去完成实例化操作。
来看ServiceLoader的提供的静态方法,这几个方法都可以用于构造ServiceLoader的实例。
public staticServiceLoaderload(Classservice) { // 获取当前线程的线程上下文类加载器实例,确保通过此classLoader也能加载到项目中的资源文件 ClassLoader cl = Thread.currentThread().getContextClassLoader(); return ServiceLoader.load(service, cl); } public staticServiceLoaderload(Classservice, ClassLoader loader) { return new ServiceLoader<>(service, loader); } public staticServiceLoaderloadInstalled(Classservice) { ClassLoader cl = ClassLoader.getSystemClassLoader(); ClassLoader prev = null; while (cl != null) { prev = cl; cl = cl.getParent(); } return ServiceLoader.load(service, prev); }
load(Class service, ClassLoader loader)是典型的静态工厂方法,直接调用ServiceLoader的私有构造器进行实例化,除了需要指定加载类的目标类型,还需要传入类加载器的实例。load(Classservice)实际上也是委托到load(Class service, ClassLoader loader),不过它使用的类加载器指定为线程上下文类加载器,一般情况下线程上下文类加载器获取到的就是应用类加载器(系统类加载器)。loadInstalled(Class service)方法又看出了"双亲委派模型"的影子,它指定类加载器为最顶层的启动类加载器,最后也是委托到load(Class service, ClassLoader loader)。
这里重点关注为什么类加载器使用线程上下文类加载器?以JDBC加载MySQL驱动举例。
下图引用自【深入理解Java类加载器(ClassLoader)】
Java类加载的过程通常是遵循双亲委派模型的。但是对于SPI接口实现类的加载就需要破坏双亲委派模型。
首先java.sql.DriverManager是由启动类加载器加载的,创建真正的Dirver对象时需要使用到mysql提供的实现:com.mysql.jdbc.Dirver,即要初始化该类。但是启动类加载器加载DirverManager的时候,使用到了启动类加载器无法加载的类,这时候就需要由系统类加载器来加载。com.mysql.jdbc.Dirver通常放在类路径下的(其实不一定)。到这里和线程上下文类加载器没由任何关系。在DriverManager中使用系统类加载的时候,可以直接使用静态方法ClassLoader.getSystemClassLoader(),但是这种情况的前提是com.mysql.jdbc.Dirver类在类路径下。如果不在类路径下,而且在系统环境中有其他的类加载器,在通过其他类加载器可能出现无法正确加载扩展点的情况。比如某个类的字节码是在数据库中存储,这时我们需要自定义一个类加载器去加载它,这个类加载器会告诉DriverManager去我们指定放的地方取。因此Thread.currentThread().setContextClassLoader(自定义加载器/默认是系统类加载器); 这个就是线程上下文类加载器起到的介质作用。线程上下文中默认放的是系统类加载器。
ServiceLoader实现了Iterable接口。
public Iteratoriterator() { // Iterator的匿名实现 return new Iterator() { // 基于实例的已加载的顺序缓存类Map的Entry迭代器实例 Iterator> 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(); } }; }
LazyIterator本身也是一个Iterator接口的实现,它是ServiceLoader的一个私有内部类。
private class LazyIterator implements IteratorServiceLoader总结{ Classservice; ClassLoader loader; // 加载的资源的URL集合 Enumerationconfigs = null; // 所有需要加载的实现类的全限定类名的集合 Iterator pending = null; // 下一个需要加载的实现类的全限定类名 String nextName = null; private LazyIterator(Class service, ClassLoader loader) { this.service = service; this.loader = loader; } public boolean hasNext() { // 如果下一个需要加载的实现类的全限定类名不为null,则说明资源中存在内容 if (nextName != null) { return true; } // 如果加载的资源的URL集合为null则尝试进行加载 if (configs == null) { try { // 资源的名称,META-INF/services/ + '需要加载的类的全限定类名' String fullName = PREFIX + service.getName(); 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; } public S next() { if (!hasNext()) { throw new NoSuchElementException(); } String cn = nextName; nextName = null; try { // 反射构造Class实例,同时进行初始化,并且强制转化为对应的类型的实例 S p = service.cast(Class.forName(cn, true, loader).newInstance()); // 添加进缓存,Key为实现类的全限定类名,Value为实现类的实例 providers.put(cn, p); return p; } catch (ClassNotFoundException x) { fail(service, "Provider " + cn + " not found"); } catch (Throwable x) { fail(service, "Provider " + cn + " could not be instantiated: " + x, x); } throw new Error(); // This cannot happen } public void remove() { throw new UnsupportedOperationException(); } }
JDK提供了一种帮第三方实现者加载服务的便捷方式,如JDBC、日志等,第三方实现者需要遵循约定把具体实现的类名放在/META-INF里。当JDK启动时会去扫描所有jar包里符合约定的类名,再调用forName进行加载,如果JDK的ClassLoader无法加载,就使用当前执行线程的线程上下文类加载器。
但是在通过SPI查找服务的具体实现时,会遍历所有的实现进行实例化,并在循环中找到需要的具体实现。
- 深入理解Java类加载器(ClassLoader)
- 双亲委派模型与线程上下文类加载器
- JDK API 文档



