简介
SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是解耦。
SPI整体机制图如下:
当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader。
基本使用
创建如下结构的maven项目:
其中app子项目maven:
<dependencies>
<dependency>
<groupId>com.wj</groupId>
<artifactId>interface</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.wj</groupId>
<artifactId>mysql-impl</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.wj</groupId>
<artifactId>redis-impl</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
我在interface项目中声明了一个接口com.wj.ISaveService
:
public interface ISaveService {
void save();
}
mysql-impl项目和redis-impl项目对ISaveService
接口实现如下:
public class MysqlSaveService implements ISaveService {
@Override
public void save() {
System.out.println("mysql save...");
}
}
public class RedisSaveService implements ISaveService {
@Override
public void save() {
System.out.println("redis save...");
}
}
mysql-impl的resource目录下新建META-INF/services/
包,创建名称为com.wj.ISaveService
的文件与interface中接口全类名相同,文件内容如下(为该接口的实现类的全类名相同):
com.wj.impl.MysqlSaveService
redis-impl与mysql-impl类似,文件内容如下:
com.wj.impl.RedisSaveService
app项目的main方法:
public class Main {
public static void main(String[] args) {
//使用ServiceLoader加载所有的ISaveService接口
ServiceLoader<ISaveService> serviceLoader = ServiceLoader.load(ISaveService.class);
for (ISaveService iSaveService : serviceLoader) {
iSaveService.save();
}
}
}
运行结果:
应用场景
SPI扩展机制应用场景有很多,比如Common-Logging,JDBC,Dubbo等等。
比如JDBC场景下:
- 首先在Java中定义了接口java.sql.Driver,并没有具体的实现,具体的实现都是由不同厂商提供。
在MySQL的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。
同样在PostgreSQL的jar包PostgreSQL-42.0.0.jar中,也可以找到同样的配置文件,文件内容是org.postgresql.Driver,这是PostgreSQL对Java的java.sql.Driver的实现。
原理
如果想要打断点调试,可以修改一下main方法:
ServiceLoader<ISaveService> serviceLoader = ServiceLoader.load(ISaveService.class);
Iterator<ISaveService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
ISaveService next = iterator.next();
next.save();
}
源码不算复杂,当调用iterator的hasNext方法时,内部使用的LazyIterator会去找到对应的文件。
可以看:
private class LazyIterator
implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//private static final String PREFIX = "META-INF/services/";
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;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//获取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());
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();
}
}
缺点
1.不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
2.获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。
3.多个并发多线程使用 ServiceLoader 类的实例是不安全的。