SPI即Service Provider Interface,Service提供者接口:提供给服务提供厂商与扩展框架功能的开发者使用的接口。
java spi提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
很多框架都使用了java的SPI机制,如JDBC4中的java.sql.Driver的SPI实现(mysql驱动、oracle驱动等)、common-logging的日志接口实现、dubbo的扩展实现等等框架;
先来看Java spi如何使用:
- 创建接口 A
- 创建实现类 A1Impl,A2Impl
- 在src/main/resources/ 下建立 /META-INF/services 目录,创建一个文件,文件名为接口 A 的全限定名(包名+类名)
- 在文件中指定一个实现类,指定谁就在文件中写上它的全限定名
- main方法:
public static void main(String[] args) {
ServiceLoader<A> s = ServiceLoader.load(A.class);
Iterator<A> searchs = s.iterator();
if(searchs.hasNext()){
A a= searchs.next();
a.xxx();
}
}
具体实现在ServiceLoader类里。
所以Java的SPI实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制.
如JDBC 数据库驱动包,mysql-connector-java-5.1.45.jar 就有一个 /META-INF/services/java.sql.Driver 里面内容是 com.mysql.jdbc.Driver 。
为什么配置文件要放在/META-INF/services/?
来分析下ServiceLoader类
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
这里获得线程上下文的ClassLoader,因为双亲委派模式下上层ClassLoader无法加载下层的类,具体见双亲委派模式破坏-JDBC
初始化了ServiceLoader对象。
public final class ServiceLoader<S>
implements Iterable<S>
{
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);
}
例子里iterator()返回的就是LazyIterator,来看看其hasNext与next方法
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);
}
}
hasNext调用了hasNextService
private static final String PREFIX = "META-INF/services/";
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//fullName = META-INF/services/java.sql.Driver
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;
}
该方法就是获取META-INF/services/xxx配置文件中你的配置项,也就是实现类的全限定名。这里就是一开始问题的答案。
next方法就是调用Class.forName通过hasNext里得到的nextName 与传过来的ClassLoader加载接口实现类。
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);
......
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
加载并初始化实现类。