SPI(Service Provider Interface)主要是被框架开发人员使用的一种技术。例如,使用 Java 语言访问数据库时我们会使用到 java.sql.Driver 接口,不同数据库产品底层的协议不同,提供的 java.sql.Driver 实现也不同,在开发 java.sql.Driver 接口时,开发人员并不清楚用户最终会使用哪个数据库,在这种情况下就可以使用 Java SPI 机制在实际运行过程中,为 java.sql.Driver 接口寻找具体的实现
Log 接口
public interface log {
void info(String msg);
void error(String msg);
}
接口实现类
log4jImpl
@Slf4j
public class log4jImpl implements log {
@Override
public void info(String msg) {
log.info("log4j: {}", msg);
}
@Override
public void error(String msg) {
log.error("log4j: {}", msg);
}
}
lombackImpl
@Slf4j
public class lombackImpl implements log {
@Override
public void info(String msg) {
log.info("lomback: {}", msg);
}
@Override
public void error(String msg) {
log.error("lomback : {}", msg);
}
}
建立配置文件
spi.log4jImpl
spi.lombackImpl
测试
*/
public class logTest {
public static void main(String[] args) {
ServiceLoader<log> serviceLoader = ServiceLoader.load(log.class);
Iterator<log> iterator = serviceLoader.iterator();
while (iterator.hasNext()){
log log = iterator.next();
log.info("java spi");
}
}
}
测试结果
Connected to the target VM, address: '127.0.0.1:746', transport: 'socket'
INFO - log4j: java spi
INFO - lomback: java spi
Disconnected from the target VM, address: '127.0.0.1:746', transport: 'socket'
安装测试程序的结果,开始进行探究。首先抛出问题。
ServiceLoader.load(log.class)
在这里方法中,我们会的到当前的类加载器(AppClassLoader), 随后调用 load 方法创建了一个 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);
}
ServiceLoader
ServiceLoader 实现了迭代器
public final class ServiceLoader<S> implements Iterable<S>
{
private static final String PREFIX = "META-INF/services/";
// The class or interface representing the service being loaded
private final Class<S> service;
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
}
ServiceLoader 的next() 、 hasNext() 方法
直观看到其调用了 nextService(); hasNextService();
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 boolean hasNext() {
if (acc == null) {
return hasNextService();
} else {
PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
public Boolean run() { return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
nextService(); hasNextService();
hasNextService() 方法用来解析文件,将文件的全类名进行校验之后,放入一个迭代器中。
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 也是一个迭代器。
pending = parse(service, configs.nextElement());
}
// 从迭代器中拿一个元素。
nextName = pending.next();
return true;
}
nextService()
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
// hasNextService() 方法执行完毕之后,会更新 nextName 字段。
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 {
// c.newInstance() 使用Class 创建这个类的实例
// service.cast() 将类实例(log接口的实现类)转化为 log 接口
S p = service.cast(c.newInstance());
// 放入链表中,在hasNextService解析文件的时候,会判断providers中是否包含待解析的 全类名。
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
因篇幅问题不能全部显示,请点此查看更多更全内容
Copyright © 2019- igat.cn 版权所有 赣ICP备2024042791号-1
违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务