SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。我们先通过一个很简单的例子来看下它是怎么用的。
一、使用
先创建一个父类:
/**
* 父类
*/
public interface SPIService {
void execute();
}
子类1:
/**
* 子类1
*/
public class SPIImpl1 implements SPIService{
@Override
public void execute() {
System.out.println("spi1 execute()");
}
}
子类2:
/**
* 子类2
*/
public class SPIImpl2 implements SPIService {
@Override
public void execute() {
System.out.println("spi2 execute()");
}
}
在resource目录下创建我们需要使用的文件夹和文件
好了,下面就是我们的测试类了
public class Test {
public static void main(String[] args) {
//第一种方式,获取SPIService的子类
Iterator<SPIService> providers = Service.providers(SPIService.class);
while (providers.hasNext()){
SPIService service = providers.next();
service.execute();
}
//第二种方式,获取SPIService的子类
ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);
Iterator<SPIService> iterable = load.iterator();
while (iterable.hasNext()){
SPIService service = iterable.next();
service.execute();
}
}
}
打印日志如下:
这样就算是完成了第一步的简单使用
二、原理
我们先讲第二种方法吧
ServiceLoader load = ServiceLoader.load(SPIService.class);
ServiceLoader实现了Iterable
public final class ServiceLoader<S> implements Iterable<S>
/**
* 从当前线程中获得类加载器,这时候获得的是applicationClassLoader
**/
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
/**
* 初始化ServiceLoader,如果loader为null,那么使用Systenm class Loader,如果失败则使用BootStrap Class Loader
**/
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
/**
* 如果loader为null,那么使用Systenm class Loader,如果失败则使用BootStrap Class Loader
* 给acc设值,SecutityManager,目前还不清楚这个做什么用的
**/
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();
}
/**
* Clear this loader's provider cache so that all providers will be
* reloaded.
*
* <p> After invoking this method, subsequent invocations of the {@link
* #iterator() iterator} method will lazily look up and instantiate
* providers from scratch, just as is done by a newly-created loader.
*
* <p> This method is intended for use in situations in which new providers
* can be installed into a running Java virtual machine.
*/
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
因为ServiceLoader实现了Iterable 所以当调用hasNext方法时,走了ServiceLoader自己的
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);
}
}
在acc为null的情况下
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
//获得系统资源,通过SystemClassLoader或者BootstrapClassLoader来加载系统资源
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;
}
上文中的parse方法就是把类名从文件中一行行解析出来
private Iterator<String> parse(Class<?> service, URL u)
throws ServiceConfigurationError
{
InputStream in = null;
BufferedReader r = null;
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
try {
if (r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
return names.iterator();
}
下面就是通过next()方法获得类对象
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);
}
}
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());
//provider暂时缓存,所以在初始化的时候要调用clear方法,清空此缓存
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}