Java SPI机制学习与常用框架SPI案例
概念
SPI(Service Provider Interface)是JDK内置的服务提供机制,常用于框架的动态扩展,类似于可拔插机制。提供方将接口实现类配置在classpath下的指定位置,调用方读取并加载。当提供方发生变化时,接口的实现也会改变。Java生态中JDK、Dubbo、Spring等都通过SPI提供了动态扩展的能力。
样例
public interface Search {
void search();
}
public class FileSearchImpl implements Search {
@Override
public void search() {
System.out.println("file search...");
}
}
public class DataBaseSearchImpl implements Search {
@Override
public void search() {
System.out.println("db search");
}
}
public class SpiTest {
public static void main(String[] args) {
ServiceLoader<Search> searches = ServiceLoader.load(Search.class);
searches.forEach(Search::search);
}
}
resources文件夹创建META-INF/services/wang.l1n.spi.Search
文件,内容为接口实现类:
wang.l1n.spi.FileSearchImpl
wang.l1n.spi.DataBaseSearchImpl
运行结果:
load加载流程
ServiceLoder.load
静态方法用于加载SPI实现类,实现逻辑如下:
- 获取当前线程类加载器和上下文信息,调用实例化方法,重新加载SPI
- 重新加载SPI的流程:
- 清空缓存providers中已实例化的SPI服务,providers是LinkedHashMap类型,用于保存已经被成功加载的SPI示例对象
- 如果providers非空,直接返回Iterator,否则返回LazyIterator的Iterator。
- 创建LazyIterator懒加载迭代器,传入SPI类型和类加载器
- 清空缓存providers中已实例化的SPI服务,providers是LinkedHashMap类型,用于保存已经被成功加载的SPI示例对象
实现代码和对应的成员变量如下:
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);
}
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 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;
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();
}
};
}
LazyIterator加载流程
加载流程参考代码注释:
private class LazyIterator
implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//获取接口的全称,拼接了META-INF/services/
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);
}
}
//解析文件内容,获取SPI接口的实现类名
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 {
//获取Class对象
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 {
//使用newInstance创建对象,并添加到providers缓存中
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
}
}
JDK SPI能实现加载扩展接口的基本要求,存在几个缺点:
- 需要遍历所有class并进行实例化,需要调用某个特定实现只能循环找。
- 无法和Spring提供的上下文融合使用。
- ServiceLoader类非线程安全
常用框架SPI案例
Spring Boot
org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames
方法用于加载所有META-INF/spring.factories
文件,主要流程如下:
- 搜索classpath下所有
META-INF/spring.factories
配置文件 - 解析文件,获取文件中对应的全限定类名
代码注释如下:
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
//读取缓存
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
//获取文件路径
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
//遍历所有路径
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
解析获取Properties对象
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
//解析properties,存放到map中
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
// Replace all lists with unmodifiable lists containing unique elements
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
Java SPI机制学习与常用框架SPI案例
https://l1n.wang/2023/Java基础/Java SPI机制学习与常用框架SPI案例/