人生有涯,学海无涯
一、SPI是什么
SPI是绝对API而言的。
API指的是利用对服务调用方提供的接口,用于提供某种服务、性能,面向的是服务调用方。
SPI指的是利用对服务实现方提供的接口,用于实现某种服务、性能,面向的是服务实现方
二、SPI的应用
2.1、创立服务接口
package com.jw.spi;
public interface Fruit {
String getName();
}
2.2、创立多个服务实现
package com.jw.spi;
public class Apple implements Fruit {
@Override
public String getName() {
return "apple";
}
}
package com.jw.spi;
public class Banana implements Fruit {
@Override
public String getName() {
return "Banana";
}
}
这里的两个服务实现类,针对的是两个服务实现方,一方实现了Apple,另一方实现了Banana。
2.3、创立配置文件
在resource下创立/META-INF/services目录,在services目录下创立以服务接口全限定名为名称的文件:com.jw.spi.Fruit
文件内容为,以后服务实现的服务实现者类的全限定名
com.jw.spi.Apple
2.4、创立测试类
public class Test {
public static void main(String[] args) {
ServiceLoader s = ServiceLoader.load(Fruit.class);
Iterator it = s.iterator();
while(it.hasNext()){
System.out.println(it.next().getName());
}
}
}
执行后果为:
apple
三、SPI的实现原理
SPI的实现次要依附的就是ServiceLoader类。应用该类加载接口类型(例如:Fruit.class)
ServiceLoader s = ServiceLoader.load(Fruit.class);
尽管是一个load办法,然而并没有加载到指定的服务实现类,这里仅仅是对加载服务实现类做一些筹备工作:
上面剖析一下ServiceLoader类的源码:
public final class ServiceLoader
implements Iterable
{
// 扫描目录前缀
private static final String PREFIX = "META-INF/services/";
// 被加载的类或接口
private final Class service;
// 用于定位、加载和实例化实现方实现的类的类加载器
private final ClassLoader loader;
// 上下文对象
private final AccessControlContext acc;
// 依照实例化的程序缓存曾经实例化的类
private LinkedHashMap providers = new LinkedHashMap<>();
// 懒查找迭代器
private LazyIterator lookupIterator;
// 创立ServiceLoader
public static ServiceLoader load(Class service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static ServiceLoader load(Class service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class svc, ClassLoader cl) {
// 为service赋值
service = Objects.requireNonNull(svc, "Service interface cannot be null");
// 为loader赋值
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
// 为acc赋值
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
// 清空providers缓存
providers.clear();
// 为lookupIterator赋值,其实就是创立一个LazyIterator提早迭代器。
lookupIterator = new LazyIterator(service, loader);
}
// 公有外部类,提供对所有的service的类的加载与实例化
private class LazyIterator implements Iterator
{
Class service;
ClassLoader loader;
Enumeration configs = null;
Iterator pending = null;
String nextName = null;
private LazyIterator(Class service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
....
}
而后创立迭代器:Iterator it = s.iterator();
public Iterator iterator() {
return new Iterator() {
Iterator> 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办法中采纳了匿名外部类的形式定义了一个新的迭代器,这个迭代器中每一个办法都是通过调用之前创立好的提早迭代器lookupIterator来实现的
最初就是进行迭代加载了。
while(it.hasNext()){
System.out.println(it.next().getName());
}
public boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 获取目录下所有的类
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()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
hasNext办法调用了提早迭代器的hasNext办法,外部调用了hasNextService办法,在这个办法中就会设法去找到指定名称(META-INF/services/+接口全限定名)的资源文件。并实现读取文件内容的操作。
而后执行it.next()操作,这个又会调用提早迭代器的对应办法hasNext,外部调用了nextService办法,这个办法次要性能就是加载下面从文件中读取到的全限定名称示意的类。并生成实例,将实例保留到providers中。
public S next() {
if (acc == null) {
return nextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public S run() { return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
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中
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的详解到此就完结了,总结下spi能带来的益处:
不须要改变源码就能够实现扩大,解耦。
实现扩大对原来的代码简直没有侵入性。
只须要增加配置就能够实现扩大,合乎开闭准则。