Java SPI扩展机制实现分析
SPI
,即Service Provider Interface
,是JDK
内置的服务发现机制。经常在实际工作中会用到java.sql.Driver
接口,不同厂商可以根据该接口提供不同的实现,比如mysql
和postgresql
都有不同的实现提供给用户,而Java
的SPI
扩展机制则可以为某个接口寻找具体的服务实现。
当服务的提供者提供了某个接口的实现后,需要在ClassPath
下的META-INF/services
目录下创建一个名为服务接口全限定名的配置文件,配置文件中的内容为这个接口具体实现类的全限定名。当其他程序需要这个服务时,在引用服务提供者的jar
包后,通过查找jar
包中META-INF/services
的服务接口配置文件,该配置文件中有接口的具体实现类名,可以根据该类名进行加载、实例化服务提供者,因此能使用该服务。而定位、查找、实例化、缓存服务提供者实例的工作由java.util.ServiceLoader
完成。
SPI扩展机制举例说明
SPI接口
/** Log interface. */
public interface Log {
/**
* Log execute method.
*/
void execute();
}
接口实现
实现一:
/** Log SPI implementation By Log4j. */
public class Log4j implements Log{
@Override
public void execute() {
System.out.println("Log4j...");
}
}
实现二:
/** Log SPI implementation By Logback. */
public class Logback implements Log{
@Override
public void execute() {
System.out.println("Logback...");
}
}
META-INF目录下增加配置文件
com.alvin.middleware.javaspi.Log4j
配置文件中指定实现类为com.alvin.middleware.javaspi.Log4j
,则SPI
加载机制只会加载这一个实现类。
com.alvin.middleware.javaspi.Log4j
com.alvin.middleware.javaspi.Logback
如果配置了多个实现类,则SPI
加载机制会加载全部的实现类,程序需通过额外的机制来选择具体使用哪一个实现的服务。
测试类
import java.util.Iterator;
import java.util.ServiceLoader;
/** Log SPI Test */
public class Main {
public static void main(String[] args) {
// 创建ServiceLoader
ServiceLoader<Log> loader = ServiceLoader.load(Log.class);
// 获取迭代器
Iterator<Log> iterator = loader.iterator();
// hasNext()调用时读取配置文件
while (iterator.hasNext()) {
// next()调用时实例化 服务提供者 实现
Log logImpl = iterator.next();
logImpl.execute();
}
}
}
说明:
ServiceLoader
在实例化之后,不会马上去加载配置文件并实例化服务提供者。配置文件的加载、解析是在获取迭代器iterator
,并调用iterator.hasNext()
的时候进行的;服务提供者类的实例化是在调用iterator.next()
方法的时候进行的。因此,这是一种懒加载的服务定位、加载、实例化机制。
以测试类分析ServiceLoader的源码及实现原理
ServiceLoader
中有6个Field
,如下:
// 接口文件所在的类路径目录
private static final String PREFIX = "META-INF/services/";
// 服务接口
private final Class<S> service;
// 定位、加载、实例化服务实现类的类加载器
private final ClassLoader loader;
// 访问控制上下文
private final AccessControlContext acc;
// 以初始化顺序缓存的服务提供者,key为服务提供者(实现类)的全限定名,value为实现类实例
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 当前的服务懒加载迭代器,真正完成迭代工作的迭代器. LazyIterator为ServiceLoader的内部类
private LazyIterator lookupIterator;
创建ServiceLoader
ServiceLoader<Log> loader = ServiceLoader.load(Log.class);
对应源码:
// 使用线程上下文类加载器创建ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service) {
// 线程上下文类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
// 根据指定的服务接口Class对象和类加载器创建ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(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,用于新的服务提供者安装到运行中的JVM的情况。
public void reload() {
// 清空providers缓存
providers.clear();
// 生成懒加载迭代器
lookupIterator = new LazyIterator(service, loader);
}
以上步骤显示了ServiceLoader
的实例化过程。
获取迭代器并进行迭代
Iterator<Log> iterator = loader.iterator();
while (iterator.hasNext()) {
Log logImpl = iterator.next();
logImpl.execute();
}
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();
}
// 不支持remove操作
public void remove() {
throw new UnsupportedOperationException();
}
};
}
从服务提供者查找过程可知:
hasNext()
: 先从providers
缓存中查找,如果有,直接返回true
;如果无,通过LazyIterator
进行查找。next()
: 先从providers
缓存中获取,如果有,直接返回服务提供者实例;如果无,通过LazyIterator
获取。
LazyIterator分析
LazyIterator
的Field
:
// 服务接口Class对象
Class<S> service;
// 类加载器
ClassLoader loader;
// 通过类加载器获取到的所有配置文件的URL
Enumeration<URL> configs = null;
// 对应单个配置文件中的所有服务实现类的全限定名ArrayList集合的迭代器
Iterator<String> pending = null;
// 下一个被加载、实例化、缓存的服务提供者类全限定名
String nextName = null;
上述的service
和loader
已经在ServiceLoader
实例化的时候赋值过。configs、pending、nextName
是在调用hasNext()、next()
时赋值的。
hasNext()方法
// 是否还有服务提供者实现
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);
}
}
// 是否还有服务提供者实现
private boolean hasNextService() {
// 当前存在还未被加载、实例化的服务提供者类,返回true
if (nextName != null) {
return true;
}
// 加载所有配置,只会加载一次,重新加载需调用reload()方法
if (configs == null) {
try {
// fullName: META-INF/services/com.alvin.middleware.javaspi.Log
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()) {
// 无配置文件可解析,返回false
return false;
}
pending = parse(service, configs.nextElement());
}
// 下一个被处理的服务提供者类
nextName = pending.next();
return true;
}
// 解析某个配置文件
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();
}
// 单行解析
// lc: line count
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);
// 检查providers缓存和names里是否已经包含了该类名(后续创建实例的时候不需要再做检查)
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
// 返回下一行的行号
return lc + 1;
}
hasNextService()
实现逻辑:
- 首先使用
ClassLoader
加载所有配置文件到configs
,比如META-INF/services/com.alvin.middleware.javaspi.Log文件
; - 对每个配置文件进行解析,因为一行一个实现类名,因此将此配置文件中的所有实现类全限定类名保存到迭代器
pending
对应的ArrayList
中。 - 最后返回
nextName
,即下一个被处理的实现类。
next()方法
// 下一个服务提供者实例
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);
}
}
// 下一个服务提供者实例
private S nextService() {
// 如果无,直接抛出异常
if (!hasNextService())
throw new NoSuchElementException();
// 本次会处理的服务提供者全限定类名
String cn = nextName;
// nextName置为null,因为本次已经处理了
nextName = null;
// cn对应的Class对象
Class<?> c = null;
try {
// 加载cn,不初始化
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());
// 放入缓存中(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
}
nextService()
实现逻辑:
- 首先加载
nextName
对应的实现类,如com.alvin.middleware.javaspi.Log4j
; - 使用指定的类加载器创建实例,并转为服务接口类型;
- 将该实例缓存在
providers
缓存中,供后续查找,并返回转型后的实现类实例。
在next()
方法返回之后,程序拿到服务提供者实现类实例,就可以完成后续的逻辑。
SPI扩展机制的特点和缺点
特点
所有的配置文件只会加载一次,服务提供者也只会被实例化一次,重新加载配置文件可使用reload
方法。
缺点
- 全部实例化:在使用
SPI
查找具体的某个实现的时候,需要遍历所有的实现,并实例化,然后在循环中才能找到需要的实现。因此需要把所有的实现都实例化了,即便不需要,也都给实例化了。 - 查找耗时:查找一个具体的实现需要遍历查找。