SPI是什么
SPI 全称为 Service Provider Interface 是一种服务提供发现机制,像Java中的数据库驱动java.sql.Driver就使用到了这种机制,还有像SpringBoot的starter加载也是使用类似的思想,Dubbo也基于JAVA SPI思想实现了一套功能更强的 SPI 机制。
简单案例
我们先通过一个简单案例来看看SPI如何使用及效果,这儿有个Robot的接口,我们通过JAVA SPI构建Robot的ServiceLoader然后调用sayHello方法。
public interface Robot {
void sayHello();
}
public class JavaSPITest {
@Test
public void sayHello() throws Exception {
ServiceLoader<Robot> serviceLoader = ServiceLoader.load(Robot.class);
System.out.println("Java SPI");
serviceLoader.forEach(Robot::sayHello);
}
}
以下的Bumblebee和OptimusPrime是Robot的实现类。
public class Bumblebee implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Bumblebee.");
}
}
public class OptimusPrime implements Robot {
@Override
public void sayHello() {
System.out.println("Hello, I am Optimus Prime.");
}
}
控制台输出:
Java SPI
源码解析
这儿为啥没有调用实现类的syaHello方法呢(解决办法在最后面,着急的读者可以直接看最后面)?接下来我们通过源码来看看ServiceLoader是如何查找到Robot的实现类并调用的,先看看load方法做了什么。
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取类加载器,默认是AppClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader){
// 创建ServiceLoader实例
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();
// 内部懒加载迭代器,用于查找provider
lookupIterator = new LazyIterator(service, loader);
}
可以发现源码创建了一个ServiceLoader对象和一个LazyIterator对象,我们看看ServiceLoader和LazyIterator的构造器做了什么和内部都有哪些属性。
public final class ServiceLoader<S>
implements Iterable<S>
{
// 查找路径固定前缀
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
// 父类或接口
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
// 类加载器
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
// 访问控制器
private final AccessControlContext acc;
// Cached providers, in instantiation order
// 缓存provider,按照实例化顺序
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
// 懒加载迭代器
private LazyIterator lookupIterator;
...
}
再看看LazyIterator。
private class LazyIterator
implements Iterator<S>
{
// 父类或者接口
Class<S> service;
// 类加载器
ClassLoader loader;
Enumeration<URL> configs = null;
// 所有provider的全限定类名
Iterator<String> pending = null;
// 下个provider的全限定类名
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
...
}
通过查看ServiceLoader和LazyIterator源码,发现两个类都是没有静态代码块的,也没有父类,那就是说ServiceLoader.load(Robot.class)这行代码仅仅是做了一些属性赋值的操作,那是如何查找和何时查找provider的呢?有些读者可能已经想到了这儿是使用了懒加载的思想,答案是否如此我们接着顺着JavaSPITest的调用代码往下看。
serviceLoader.forEach(Robot::sayHello)这儿是因为ServiceLoader实现了Iterable接口,我们看看ServiceLoader内部是如何实现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();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
看源码我们可以发现,iterator方法直接创建了一个匿名内部类,只有一个knownProviders属性,这个属性是匿名内部类被创建时直接拿取外部类ServiceLoader的providers属性的Iterator。除此之外hasNext和next方法都是先直接调用knownProviders的hasNext和next方法,查不到再通过lookupIterator查询,其实这儿的knownProviders就相当于一个缓存,提高性能用的。
之前我们看过ServiceLoader的源码,刚开始的时候providers是没有值的,那么匿名内部类的hasNext和next方法会直接调用lookupIterator的hasNext和next方法,我们看看lookupIterator的完整代码。
private class LazyIterator
implements Iterator<S>
{
// 父类或者接口
Class<S> service;
// 类加载器
ClassLoader loader;
Enumeration<URL> configs = null;
// 所有provider的全限定类名
Iterator<String> pending = null;
// 下个provider的全限定类名
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
// 初次进来肯定是null,进入内部开始初始化
if (configs == null) {
try {
// 先构建资源文件的完整路径,然后加载资源文件
// 这儿的fullName就是META-INF/services/com.demo.spi.Robot
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);
}
}
// 刚开始pending也是null,直接通过上面加载好的configs构建pending
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// parse方法内部按照行读取赋值
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 通过java反射构建class
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());
// 放到provider中
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
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);
}
}
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);
}
}
public void remove() {
throw new UnsupportedOperationException();
}
}
hashNext内部调用了hasNextService方法,hasNextService先是去加载META-INF/services/com.demo.spi.Robot文件,然后按照行读取放到pending中,nextName用于遍历pending,这个pending中的每个String用来做什么呢?我们看看next方法。
next方法调用了nextService方法,nextService直接通过nextName反射构建目标class类,然后调用class的newInstance方法创建目标class的实例对象,放到provider中,这样外面就可以通过provider调用Robot实现类的sayHello方法了。
配置文件
通过源码我们可以知道除了Robot接口、Robot的实现类、调用代码外我们还需要在META-INF/services/文件夹下创建com.demo.spi.Robot文件,里面每一行都是Robot接口的实现类的全限定类名,如下:
再运行JavaSPITest的代码,控制台输出:
Java SPI
Hello, I am Bumblebee.
Hello, I am Optimus Prime.
知识小结
- JAVA SPI采用了懒加载,在真正遍历使用的时候才会去加载类。
- JAVA SPI是通过加载读取META-INF/services/文件夹下的接口全限定类名文件(如文中的com.demo.spi.Robot文件),文件中的每一行都需要是接口的实现类的全限定类名,ServiceLoader内部会通过Class.forName反射创建接口实现类实例并维护。
- JAVA SPI实现了接口和实现解偶,这也是面向接口编程的一种体现。