最近阅读源码时看到了SPI加载类的方式 这里学习一下
SPI Service Provider Interface
服务提供接口
API和SPI区别
API是接口和实现类均有服务提供方负责 服务调用方更多是一种主动调用 只是调用SDK来实现某个功能;接口的归属权在于服务提供方
SPI是接口在调用方侧定义,但是具体实现类由服务提供方现 接口归属权在于服务调用方
SPI实践
定义一个类 和三个实现
public interface ISpiService {
public void diBiz();
}
public class ASpiService implements ISpiService {
@Override
public void diBiz() {
System.out.println("ASpiService diBiz");
}
}
public class BSpiService implements ISpiService {
@Override
public void diBiz() {
System.out.println("BSpiService diBiz");
}
}
public class CSpiService implements ISpiService {
@Override
public void diBiz() {
System.out.println("CSpiService diBiz");
}
}
定义实现类的目录文件
/META-INF/services 目录下
文件名为接口的全限定名
文件类型为实现类的全限定名
com.java.spi.ASpiService
com.java.spi.BSpiService
com.java.spi.CSpiService
使用ServiceLoad来加载实现类
public static void main(String[] args) {
ServiceLoader<ISpiService> spiServices = ServiceLoader.load(ISpiService.class);
Iterator<ISpiService> iterator = spiServices.iterator();
while (iterator.hasNext()) {
iterator.next().diBiz();
}
}
运行结果
ASpiService diBiz
BSpiService diBiz
CSpiService diBiz
可知可以调用所有类实现类的方法
ServiceLoad大致原理
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
类加载器使用线程上下文加载器
最终load方法初始化lazyIterator对象
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
hasNext方法
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 static final String PREFIX = "META-INF/services/";
从约定目录下找实现类
next方法
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
}
使用反射根据类名来初始化对象
SPI应用
JDBC
日志框架slf4j