持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第4天,点击查看活动详情
SPI是什么
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。
使用SPI机制的好处就在于使得模块间能基于接口编程,而不用对实现类进行硬编码。
Java SPI的使用
步骤
- 接口实现类需要在jar包的META-INF/services目录下创建一个文件,文件名为“接口全限定名”,内容为实现类的全限定名。
- 接口实现类必须带一个无参构造器。
- 通过java.util.ServiceLoder#load(...)方法可以加载实现类对象。
Demo
接口:
package world.shuashua;
public interface Handler {
void doHandler();
}
实现类-1:
package world.shuashua;
public class HandlerImplOne implements Handler {
public void doHandler() {
System.out.println("一号机正在处理");
}
}
实现类-2:
package world.shuashua;
public class HandlerImplTwo implements Handler {
public void doHandler() {
System.out.println("二号机正在处理");
}
}
配置文件:
使用ServiceLoader#load(...)方法加载实现类
package world.shuashua;
import java.util.ServiceLoader;
public class Main {
public static void main(String[] args) {
ServiceLoader<Handler> handlers = ServiceLoader.load(Handler.class);
for (Handler handler : handlers) {
handler.doHandler();
}
}
}
原理剖析
ServiceLoder成员变量
// 加载的配置文件前缀,这也就是为什么文件要建在META-INF/services/目录下。
// 另外这个路径是final修饰的,并且已赋值,是不可更改的。
private static final String PREFIX = "META-INF/services/";
// 需要被加载和实例化的接口或类
private final Class<S> service;
// 加载和实例化providers的类加载器
private final ClassLoader loader;
// 创建ServiceLoader时采用的访问控制上下文
private final AccessControlContext acc;
// providers的缓存, 使用LinkedHashMap能按顺序存储实例化的顺序。
// providers:<实现类全限定名,实现类的实例对象>
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 懒加载的查找迭代器
private LazyIterator lookupIterator;
ServiceLoder#load(...)方法调用过程分析
分析:ServiceLoader handlers = ServiceLoader.load(Handler.class);
ServiceLoader#load(Class<S> service)ServiceLoader#load(Class<S> service,ClassLoader loader)ServiceLoader#ServiceLoader(Class<S> svc, ClassLoader cl)->构造器方法ServiceLoader#reload()
ServiceLoader#load(...)
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(...) 【私有的构造器方法】
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null"); // 检查svc是否为null
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
ServiceLoader#reload()
public void reload() {
providers.clear(); // 清空map
lookupIterator = new LazyIterator(service, loader); // 创建懒加载的迭代器对象。此时并未加载类和实例化对象
}
实例化对象的时机
private S nextService() {
if (!hasNextService()) // 如果没有下一个实现类了
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader); // 使用loader加载实现类的Class对象,但不初始化
} 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); // 将 <实现类全限定名,实现类的实例对象> 放入providers
return p;
} catch (Throwable x) {
fail(service,"Provider " + cn + " could not be instantiated",x);
}
throw new Error(); // This cannot happen
}
缺点
- 不能按需加载,虽然其存在懒加载机制。
- 多个并发多线程使用ServiceLoader类的实例是不安全的