介绍: Java SPI 全称 Java Service Provider Interface,是 Java 提供的一种服务提供者发现机制。其核心功能是通过接口找到其实现类。在实际运用中,主要用于在程序启动或运行时,通过 SPI 机制,加载并装配接口实现类,实现组件的替换和动态扩展。
使用步骤:
- 创建一个接口,该接口描述了您希望提供的组件的行为。
- 创建一个实现该接口的类,该类实现了接口中定义的行为。
- 创建一个名为 "META-INF/services" 的文件夹,并在该文件夹中创建一个名为“接口全名”的文件。文件中包含了实现该接口的所有类的完全限定类名(一行一个)。
- 将实现类打包为一个 jar 文件,并将其放在应用程序的类路径中。
- 在应用程序中,调用 Java 的 ServiceLoader.load 方法来加载实现了指定接口的类。该方法返回一个 Iterable 对象,您可以使用该对象遍历所有已加载的组件并使用它们。
例如,假设您创建了名为 Example 的接口和一个实现该接口的类 ExampleImpl。
我就以JDK本身自带的JDBC接口类 java.sql.Driver 为例子,先看其操作步骤,再来看一下源码。
在我们使用 MySQL 或 Oracle 数据库时,只需要引入 MySQL 驱动 jar 包或 Oracle 驱动 jar 包就可以了。它是怎么做到的呢?关键点是DriverManager:
//DriverManager使用SPI加载Driver扩展实现,如com.mysql.jdbc.Driver
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
这里我们来看看ServiceLoader.load 做了什么事情:
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
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();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
在ServiceLoader的构造函数里,主要是对各个成员变量做初始化,没有复杂的逻辑。
至此ServiceLoader的初始化就完成了。
通过对ServiceLoader.load的分析发现,ServiceLoader.load其实只是做了初始化,并没有真正的加载实现类.
实现类的解析、加载和实例化
使用 Java SPI 的第二步是遍历。Java SPI 正是在遍历过程中实现的,实现类的解析、加载和实例化。
while(driversIterator.hasNext()) {
driversIterator.next();
}
因为ServiceLoader实现了Iterable接口,所以它支持了遍历功能,而遍历功能的实现是通过复写iterator()方法实现,跟进去ServiceLoader.iterator()
//ServiceLoader.iterator()
public Iterator<S> iterator() {
//直接实例化并返回了一个Iterator
return new Iterator<S>() {
//实例化遍历器时,将ServiceLoader已经实例化的实现类赋值给了成员变量knownProviders。
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
//iterator.hasNext()会调用这个方法,判断是否还有实现类
public boolean hasNext() {
//先判断已加载的实现类中是否存在,存在的话直接返回true
if (knownProviders.hasNext())
return true;
//如果不存在,则调用ServiceLoader中的lookupIterator,看是否存在。
return lookupIterator.hasNext();
}
//iterator.next()会调用这个方法,获取下一个实现类
public S next() {
//如果已加载的实现类中存在,则返回已加载的实现类
if (knownProviders.hasNext())
return knownProviders.next().getValue();
//否则,调用ServiceLoader中的lookupIterator,获取下一个实现类。
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
通过以上代码分析可知:
- ServiceLoader通过复写iterator()方法实现了遍历功能;
- ServiceLoader的遍历器提供了一个简单缓存功能knownProviders,用于缓存已经加载并实例化的实现类;
- ServiceLoader的遍历器非常简单,核心逻辑是通过lookupIterator.hasNext()和lookupIterator.next()实现的; 所以下面重点要分析ServiceLoader的LazyIterator lookupIterator。 先看LazyIterator的几个核心成员变量:
//LazyIterator的成员变量
//要加载的类或接口
Class<S> service;
//类加载器
ClassLoader loader;
//加载的文件路径(META-INF/services/接口或类全限定名)
Enumeration<URL> configs = null;
//从文件路径中加载到的实现类全限定名
Iterator<String> pending = null;
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//全路径名=META-INF/services/ + 类或接口的全限定名
String fullName = PREFIX + service.getName();
//加载所有的全路径名文件
//如:META-INF/services/com.yuqiao.deeplearningdubbo.javaspi.protocol.v2.IProtocol
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;
}
//解析文件中所有的实现类名
//如:com.yuqiao.deeplearningdubbo.javaspi.protocol.v2.tcp.TcpProtocol
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
LazyIterator的hasNextService()方法,有几个关键点:
根据类或接口的全限定名加载配置文件; 解析文件中的所有实现类名; 遍历解析到的实现类名,作为下一个类名返回。 以上内容的核心是完成了实现类的解析!接下来就是实现类的加载和实例化。
我们接着看LazyIterator的nextService()方法:
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());
//将实例化的类保存到缓存中,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
}
至此 SPI 的所有实现类已经初始化完成。