SPI 全称为 Service Provider Interface,是JDK内置的一种服务提供发现机制。简单来说,它就是一种动态替换发现机制。例如:有个接口想在运行时才发现具体的实现类,那么你只需要在程序运行前添加一个实现即可,并把新加的实现描述给JDK即可。此外,在程序的运行过程中,也可以随时对该描述进行修改,完成具体实现的替换。
Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。
这些 SPI 的接口是由 Java 核心库来提供,而SPI的实现则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)中。例如:JDBC 的实现 mysql 就是通过 maven 被依赖进来。
Java SPI 实际上是 基于接口的编程+策略模式+配置文件 组合实现的动态加载机制。
系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
Java SPI 就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似 IOC 的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。所以 SPI 的核心思想就是解耦。
那么问题来了,SPI的接口是Java核心库的一部分,是由引导类加载器(Bootstrap Classloader)来加载的。SPI 的实现类是由系统类加载器(System ClassLoader)来加载的。
引导类加载器在加载时是无法找到 SPI 的实现类的,因为双亲委派模型中规定,引导类加载器 BootstrapClassloader 无法委派系统类加载器 AppClassLoader 来加载。这时候,该如何解决此问题?
线程上下文类加载由此诞生,它的出现也破坏了类加载器的双亲委派模型,使得程序可以进行逆向类加载。
概括地说,适用于:调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略
比较常见的例子:
下面,我们就用具体的代码来说明逆向类加载。不过,在距离之前,还是想对spi的使用进行一个简单的说明。
既然是 spi,那么就必须先定义好接口。其次,就是定义好接口的实现类。
在项目的 \src\main\resources\ 下创建 \META-INF\services 目录
在上面 META-INF\services 的目录下再增加一个配置文件,这个文件必须以接口的全限定类名保持一致,例如:com.xxx.xxx.HelloService
上面介绍 spi 时说道,除了代码上的接口实现之外,你还需要把该实现的描述提供给JDK。那么,此步骤就是在配置文件中撰写接口实现描述。很简单,就是在配置文件中写入具体实现类的全限定类名,如有多个便换行写入。
编写 main() 方法,输出测试接口。使用 JDK 提供的 ServiceLoader.load() 来加载配置文件中的描述信息,完成类加载操作。
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 java.util.ServiceLoader.LazyIterator lookupIterator;
// 私有内部类,提供对所有的service的类的加载与实例化
private class LazyIterator implements Iterator<S> {
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
String nextName = null;
//...
private boolean hasNextService() {
if (configs == null) {
try {
//获取目录下所有的类
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
//...
}
//....
}
}
private S nextService() {
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//反射加载类
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
}
try {
//实例化
S p = service.cast(c.newInstance());
//放进缓存
providers.put(cn, p);
return p;
} catch (Throwable x) {
//..
}
//..
}
}
}
spi 加载的主要流程供参考


