什么是SPI机制
SPI的全称是Service Provider Interface, 是JDK内置的一种服务提供发现机制,是一套用来实现第三方或者扩展第三方的API。SPI机制的实现是通过接口+配置文件+策略模式的动态加载机制。
SPI机制的约束
当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。
SPI机制的使用场景
- JDBC加载不同类型的数据库驱动
- 日志门面接口实现类加载,SLF4J加载不同提供商的日志实现类
- Spring中大量使用了SPI,
- 对servlet3.0规范
- 对ServletContainerInitializer的实现
- 自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等
- Dubbo里面有很多个组件,每个组件在框架中都是以接口的形成抽象出来!具体的实现又分很多种,在程序执行时根据用户的配置来按需取接口的实现
SPI示例代码
文件结构如下所示:
代码如下所示:
接口和实现类的定义
// 接口定义
public interface ILogService {
void warn (String msg);
}
//第一个实现类
public class ConsoleLogServiceImpl implements ILogService {
@Override
public void warn(String msg) {
System.out.println("Console log:" + msg);
}
}
//第二个实现类
public class FileLogServiceImpl implements ILogService {
@Override
public void warn(String msg) {
System.out.println("File log:" + msg);
}
}
META-INF文件的名字就是接口的全限定名。文件的内容就是接口的实现类的全限定名。
com.bobo.spi.test.service.impl.ConsoleLogServiceImpl
com.bobo.spi.test.service.impl.FileLogServiceImpl
测试类如下所示:
public class TestMain {
private static ServiceLoader<ILogService> services = ServiceLoader.load(ILogService.class);
public static void main(String[] args) {
for (ILogService service : services) {
service.warn("Hello SPI !");
}
}
}
SPI源码分析
从测试类中我们可以看出加载接口的实现类的时候使用了ServiceLoader.load(ILogService.class);通过一步一步的点进去我们发现了其实是一个构造函数
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);
}
在reload方法中创建了一个LazyIterator对象。
当我们调用迭代器的时候其实是调用了nextService方法。
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(); // This cannot happen
}
通过debug我们可以发现hasNextService方法。首先通过全路径 String fullName = PREFIX + service.getName();获取我们定义在META-INF.services中定义的文件名
然后通过 pending = parse(service, configs.nextElement());方法获取了文件中的内容
在nextService方法中获取了实现类的全路径名字,通过类加载器 c = Class.forName(cn, false, loader);创建了一个实现类的对象。
这个就是SPI机制的实现原理。