简介
SPI全名为Service Provider Interface 作用:为接口自动寻找实现类 实现方式: 1.标准制定者制定接口 2.不同厂商编写针对于该接口的实现类, 并在jar的“classpath:META-INF/services/全接口名称” 文件中指定相应的实现类全类名 3.开发者直接引入相应的jar,就可以实现为接口自动寻找实现类的功能
代码示例
Log接口
public interface Log {
void execute();
}
复制代码
Log4j类
public class Log4j implements Log {
@Override
public void execute() {
System.out.println("log4j...");
}
}
复制代码
Logback类
public class Logback implements Log {
@Override
public void execute() {
System.out.println("logback ...");
}
}
复制代码
Main类
public class Main {
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.execute();
}
}
}
复制代码
在resources下创建META-INF/services目录 并创建Log全路径名的文件com.ye.spi.Log 文件里写实现类全路径,可以写多个 写多个时,可以自己实现逻辑选择对应实现 com.ye.spi.Log4j
直接启动Main类就可以得到想要的效果
源码解析
public class Main {
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.execute();
}
}
}
复制代码
1. ServiceLoader.load() 2. serviceLoader.iterator() 3. iterator.hasNext() 4. iterator.next()
第一个方法是ServiceLoader.load()
先看ServiceLoader的属性 private static final String PREFIX="META-INF/services/";//定义实现类的接口文件所在的目录 private final Class service;//接口 private final ClassLoader loader;//定位、加载、实例化实现类 private final AccessControlContext acc;//权限控制上下文 private LinkedHashMap providers = new LinkedHashMap<>();//以初始化的顺序缓存<接口全名称, 实现类实例> private LazyIterator lookupIterator;//真正进行迭代的迭代器
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);
}
复制代码
看上面两段代码,load方法最终是调用传参的构造函数new ServiceLoader<>(service, loader) 再继续看new ServiceLoader<>(service, loader)代码
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);
}
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
复制代码
1. 赋值ServiceLoader的service 2. 赋值ServiceLoader的loader 3. 调用reload方法 4. 清空ServiceLoade的providers 5. new LazyIterator对象
综上所述,ServiceLoader.load()其实就是初始化了ServiceLoader对象和LazyIterator对象
第二个方法是serviceLoader.iterator()
public Iterator<S> iterator() {
return new Iterator<S>() {
Iterator<Map.Entry<String,S>> 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();
}
};
}
复制代码
获取迭代器, 初始化knownProviders,第一次是空的 实现了hasNext方法和next方法
iterator.hasNext()
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
复制代码
先判断knownProviders有没有,如果有就返回true 如果没有调用lookupIterator.hasNext() 可以认为knownProviders是一个缓存,后面会看到什么时候有值的
先看一下lookupIterator即LazyIterator这个类的属性 Class service;//接口 ClassLoader loader;//类加载器 Enumeration configs = null;//存放配置文件 Iterator pending = null;//存放配置文件中的内容,并存储为ArrayList,即存储多个实现类名称 String nextName = null;//当前处理的实现类名称
LazyIterator.hasNext()方法
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);
}
}
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;
}
复制代码
其实调用的是LazyIterator.hasNextService()方法 1. nextName如果有,直接返回true 2. 找到对应目录文件 3. 解析文件内容 4. 赋值给nextName 5. 返回true
LazyIterator.next()
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
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);
}
}
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
}
复制代码
1. 先判断knownProviders有没有,如果有就直接取 2. 调用nextService获取 3. 通过nextName加载对应的类,即实例化 4. 把实例化的对象放入providers中,下次就可以在这里面取,不用在实例化了 5. 返回实例化的对象
小结:spi大致流程就讲完了
1. 初始化 ServiceLoader和LazyIterator 2. 通过load传入的接口找到对应目录的文件 3. 解析文件找到类全路径名 4. 通过全路径名加载实例化类 5. 放入缓存并返回该对象