
引语
平时API倒是听得很多?SPI又是啥.别急我们来先看看面向界面程式设计的呼叫关系,来了解一下,API和SPI的相似和不同之处。
SPI理解
先来一段官话的介绍:SPI 全称为 (Service Provider Interface) ,是JDK内建的一种服务提供发现机制.(听了一脸懵逼)好的,我们结合图片来理解一下。

简单的来说分为呼叫方,界面,服务方.界面就是协议,契约,可以呼叫方定义,也可以由服务方定义,也就是界面是可以位于呼叫方的包或者服务方的包. 1.界面的定义和实现都在服务方的时候,仅暴露出界面给呼叫方使用的时候,我们称为API; 2.界面的定义在呼叫方的时候(实现在服务方),我们给它也取个名字--SPI。 应该还比较好理解吧?
SPI的使用场景
SPI在框架中其实有很多广泛的应用,这里列举几个例子: 1.Mysql驱动的选择driverManager根据配置来确定要使用的驱动;
2.dubbo框架中的扩充套件机制
使用例项
看完上面的简介和SPI在框架中的应用,想必对SPI在读者的大脑中已经产生了一个雏形,talk is cheap!show me the code.说了这么多,我们具体写一个简单的例子来看看效果,验证一下SPI.
1.首先定义一个界面,忍者服务界面
public interface NinjaService {
void performTask();
}
2.接下来写两个实现类,ForbearanceServiceImpl(上忍),ShinobuServiceImpl(下忍)
public class ForbearanceServiceImpl implements NinjaService {
@Override
public void performTask() {
System.out.println("上忍在执行A级任务");
}
}
public class ShinobuServiceImpl implements NinjaService {
@Override
public void performTask() {
System.out.println("下忍在执行D级任务");
}
}
3.接下来我们在main/resources/下建立META-INF/services目录,并且在services目录下建立一个com.scott.java.task.spi.NinjaService(忍者服务类的全限定名)的档案.
4.建立一个Client场景类来呼叫看看结果

很完美的呼叫了两个实现类的performTask()方法.
5.最后贴一下目录结构

SPI源代码简单分析
1.先看下核心类ServiceLoader的定义和属性
// 继承了Iterable类 遍历的时候使用
public final class ServiceLoader implements Iterable
{
// 这就是为啥需要在META-INF/services/目录下建立服务类的档案
private static final String PREFIX = "META-INF/services/";
// 被载入的服务
private final Class service;
// 类载入器
private final ClassLoader loader;
// 访问控制类
private final AccessControlContext acc;
// 实现类的快取 根据初始化的顺序 也就是在/services/档案中的定义顺序来定义的载入顺序
private LinkedHashMap providers = new LinkedHashMap();
// 懒载入iterator
private LazyIterator lookupIterator;
2.然后从client开始,然后依次debug进去
ServiceLoader ninjaServices = ServiceLoader.load(NinjaService.class);
public static ServiceLoader load(Class service) {
// 获取当前的类载入器 也就是AppClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static ServiceLoader load(Class service, ClassLoader loader) { return new ServiceLoader(service, loader); }
后面的就省略了,因为这里仅仅就是根据NinjaService初始化的事项,没有什么很难理解的点.
3.我们在看看具体的呼叫过程,这里使用的是client对应的class档案,因为增加for(foreach)在java中是个语法糖,实际上编译后是这样的内容
public static void main(String[] args) {
ServiceLoader ninjaServices = ServiceLoader.load(NinjaService.class);
// 这里一下其实就是增加for解糖后的程式码 有兴趣可以去了解下java的语法糖
Iterator var2 = ninjaServices.iterator();
while(var2.hasNext()) {
NinjaService item = (NinjaService)var2.next();
item.performTask();
}
}
4.随着断点继续走,我们进入到var2.hasNext()的方法
public boolean hasNext() {
// knownProviders还没有载入过provider 走下面的分支
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
这里lookupIterator上面ServiceLoader的属性介绍过,它其实是ServiceLoader中的一个Iterator的内部类。然后呼叫了内部类Iterator的hasNext()方法。
public boolean hasNext() {
// acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
// ServiceLoader初始化没有设定过securityManager,所以acc是null,进入hasNextService()
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
5.hasNextService()分析
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 这里载入了META-INF/services下的档案 也就是含有两个实现类全限定名的档案
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 因为loader是不为null 的AppClassLoader
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;
}
6.继续看到parse方法,这里最后返回的是含有两个全限定类名的Iterator,其实就是把services/下的档案内容给加载出来
private Iterator parse(Class> service, URL u) throws ServiceConfigurationError {
InputStream in = null;
BufferedReader r = null;
ArrayList 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();
}
附带的说一下parseLine(service, u, r, lc, names),检查类名是否符合规范,符合的话新增到Iterator中,到这里var2 .hasNext()执行完毕,结果是载入了services下的档案内容
private int parseLine(Class> service, URL u, BufferedReader r, int lc,
List names)
throws IOException, ServiceConfigurationError
{
String ln = r.readLine();
if (ln == null) {
return -1;
}
int ci = ln.indexOf(\'#\');
if (ci >= 0) ln = ln.substring(0, ci);
ln = ln.trim();
int n = ln.length();
if (n != 0) {
if ((ln.indexOf(\' \') >= 0) || (ln.indexOf(\' \') >= 0))
fail(service, u, lc, "Illegal configuration-file syntax");
int cp = ln.codePointAt(0);
if (!Character.isJavaIdentifierStart(cp))
fail(service, u, lc, "Illegal provider-class name: " + ln);
for (int i = Character.charCount(cp); i cp = ln.codePointAt(i);
if (!Character.isJavaIdentifierPart(cp) && (cp != \'.\'))
fail(service, u, lc, "Illegal provider-class name: " + ln);
}
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
}
return lc + 1;
}
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());
}
// 这里将下一个实现类的名字赋值给了LazyIterator的属性nextName
nextName = pending.next();
return true;
}
7.接下来执行的是 NinjaService item = (NinjaService)var2.next()的next(方法),然后继续debug进去,这里我省略了一些方法的呼叫,只展示出有用的方法这个是ServiceLoader的内部类LazyIterator的nextService()方法.
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class> c = null;
try {
// 这里nextName在上面已经赋值过了 所以反射建立例项
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());
// 将类新增到ServiceLoader的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
}
8.到这里子类的实现类返回,分析就结束了.
总结:
1.了解了什么是SPI;
2.SPI和API的简单区别和联络;
3.学习了怎么使用SPI来扩充套件服务;
4.分析了ServiceLoader的源代码载入过程,这里扯一句,简单的就是META-INF/services定义好要实现的界面(档名)和实现类(档案内容), ServiceLoader载入的时候没有例项化实现类,而是在Iterator遍历的时候去用反射建立了例项.
关注、转发、评论头条号每天分享java 知识,私信回复“源代码”赠送Spring源代码分析、Dubbo、Redis、Netty、zookeeper、Spring cloud、分散式资料





























