What is SPI?
Wiki:Service provider interface (SPI) is an API intended to be implemented or extended by a third party. It can be used to enable framework extension and replaceable components.
服务提供者接口( SPI ) 是一种旨在由第三方实现或扩展的API。它可用于启用框架扩展和可替换组件。
Ex
- 创建一个接口/抽象类
- 实现该接口/继承抽象类
- resources 目录下创建 META-INF/services (后面会说为什么是这个目录)
- 创建文件(该接口/抽象类的全限定类名),文件内容为该接口/抽象类下的 实现/子类
- 使用 ServiceLoad.load(interface/abstract class) 加载
示例代码:Ex repo
原理解析
- SPI 关键在于 ServiceLoad
private static final String PREFIX = "META-INF/services/";
我们可以看到 PREFIX 变量指定了该路径,也就是为什么要在 resource 下面创建该目录
- load 方法
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
最后会调用 ServiceLoader 的构造方法
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);
}
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(); // This cannot happen
}
最后可以发现使用的是 ClassLoader 进行加载,使用当前线程的 ClassLoader,如果等于 null,则使用 SystemClassLoader, 这里不再说明 ClassLoader双亲委派机制
观察 nextService方法,我们可以看到通过Class.forName() 进行加载类,因为我们之前在 resources 目录下定义了实现/继承类的全限定类名, 最后通过 c.newInstance() 进行实例化,至此我们完成了类的加载,以及类实例的创建
Why use SPI?
通过观察,我们发现SPI可以通过外部加载来提供扩展,比较灵活。
也因为外部加载使得依赖解耦,让类的实现/继承分离,在设计原则上这是一种实现扩展性的手段
总结
SPI 的原理其实就是类加载机制原理,通过类加载器加载类,然后在实例化类
通过 SPI PREFIX 变量我们也可以推论出,我们可以自己实现一套规则,加载自定义目录下的类
Dubbo RPC 也用了 SPI 机制,当然现在还没有深入研究去看实现原理,不过大致的思路也应该能猜到了
引用