一、简介
SPI全称Service Provider Interface,它是JDK内置的一种可以动态发现服务的机制。通过这种方式,可以方便地将服务提供者与第三方实现客户端解耦。它主要包含三个基本组件:服务接口,提供者注册API以及服务访问API。
二、使用场景
Java SPI实际上就是“面向接口编程+策略模式+配置文件”组合实现动态加载机制,多用于各种框架中,通过暴露扩展点,实现对框架特定的点进行定制,提升框架灵活性。
1. SPI应用实例Java SQL Driver(MySQL, Oracle, SqlServer)
SLF4J(log4j, logback)
Dubbo
Spring(Converter, Formatter)
三、示例
1. 定义一个接口public interface ProgramLanguage {
String hello();
}
2. 编写接口实现类public class Chinese implements Language {
@Override
public String hello() {
return "你好";
}
}
public class English implements Language {
@Override
public String hello() {
return "Hello";
}
}
3. 配置文件
创建/resources/META-INF/services,并在services文件夹下创建以接口全限定名称命名的文件,并在文件中写入实现类的全限定名com.example.spi.api.impl.English
com.example.spi.api.impl.Chinese
4. 测试public class App {
public static void main(String[] args) {
ServiceLoader loader = ServiceLoader.load(Language.class);
for (Language language : loader) {
System.out.println(language.hello());
}
}
}
输出
完整代码放在GitHub
四、原理
首先看下ServiceLoader继承关系,从这里看出为什么我们可以对loader对象进行foreach操作
接下来看iterator方法,返回了一个Iterator实例private LinkedHashMap providers = new LinkedHashMap<>();
private LazyIterator lookupIterator;
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();
}
};
}
继续跟踪lookupIterator.hasNext()public boolean hasNext() {
if (acc == null) {
// 查找Service
return hasNextService();
} else {
PrivilegedAction action = new PrivilegedAction() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
private static final String PREFIX = "META-INF/services/";
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;
}
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.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error();
}
从上面的代码可以看出,ServiceLoader读取文件的内容,并尝试将读取到的每一行加载对应类,并实例化对象,这样便完成了服务查找过程。
注意,由于使用这里通过newInstance()方法实例化对象(即通过默认构造方法创建对象),所以Service必须保留默认构造方法。