Android-SPI学习笔记
作者:苍耳 时间:2022-05-15 17:35:33
目录
概述
基本使用
1. 在低层 module_common 中声明服务
2. 在上层 module 中实现服务
3. 在其它上层 module 中使用服务
ServiceLoader.load
ServiceLoader实例创建
LazyIterator
总结
SPI的优点
SPI的缺点
概述
SPI(Service Provider Interface, 服务提供方接口),服务通常是指一个接口或者一个抽象类,服务提供方是对这个接口或者抽象类的具体实现,由第三方来实现接口提供具体的服务。通过解耦服务与其具体实现类,使得程序的可扩展性大大增强,甚至可插拔。基于服务的注册与发现机制,服务提供者向系统注册服务,服务使用者通过查找发现服务,可以达到服务的提供与使用的分离。
可以将 SPI 应用到 Android 组件化中,很少直接使用 SPI,不过可基于它来扩展其功能,简化使用步骤。
基本使用
1. 在低层 module_common 中声明服务
public interface IPrinter {
void print();
}
2. 在上层 module 中实现服务
// module_a -- implementation project(':module_common')
// com.hearing.modulea.APrinter
public class APrinter implements IPrinter {
@Override
public void print() {
Log.d("LLL", "APrinter");
}
}
// src/main/resources/META-INF/services/com.hearing.common.IPrinter
// 可以配置多个实现类
com.hearing.modulea.APrinter
// ----------------------------------------------------------------//
// module_b -- implementation project(':module_common')
// com.hearing.moduleb.BPrinter
public class BPrinter implements IPrinter {
@Override
public void print() {
Log.d("LLL", "BPrinter");
}
}
// src/main/resources/META-INF/services/com.hearing.common.IPrinter
com.hearing.moduleb.BPrinter
3. 在其它上层 module 中使用服务
// implementation project(':module_common')
ServiceLoader<IPrinter> printers = ServiceLoader.load(IPrinter.class);
for (IPrinter printer : printers) {
printer.print();
}
ServiceLoader.load
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) {
// 创建 ServiceLoader 实例
return new ServiceLoader<>(service, loader);
}
ServiceLoader实例创建
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
reload();
}
// Clear this loader's provider cache so that all providers will be reloaded.
public void reload() {
providers.clear();
// 创建了一个懒迭代器
lookupIterator = new LazyIterator(service, loader);
}
LazyIterator
ServiceLoader 实现了 Iterable 接口,可以使用 iterator/forEach 方法来迭代元素,其 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() {
// 如果 knownProviders 缓存中已经存在,则直接返回,否则加载
if (knownProviders.hasNext()) return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
上面使用了懒加载的方式,不至于一开始便去加载所有服务实现,否则反射影响性能。LazyIterator 类如下:
private static final String PREFIX = "META-INF/services/";
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 {
// 获取服务配置文件
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 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) {
// throw Exception
}
if (!service.isAssignableFrom(c)) {
// throw Exception
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
// throw Exception
}
throw new Error(); // This cannot happen
}
public boolean hasNext() {
return hasNextService();
}
public S next() {
return nextService();
}
public void remove() {
throw new UnsupportedOperationException();
}
}
总结
ServiceLoader 的原理比较简单,其实就是使用一个懒迭代器,用时加载的方式可以减少性能损耗,在加载新服务的时候通过解析服务配置文件获取配置的服务,然后通过类加载器去加载配置的服务实现类,最后将其实例返回。
SPI的优点
只提供服务接口,具体服务由其他组件实现,接口和具体实现分离。
SPI的缺点
配置过于繁琐
具体服务的实例化由ServiceLoader反射完成,生命周期不可控
当存在多个实现类对象时,ServiceLoader只提供了一个Iterator,无法精确拿到具体的实现类对象
需要读取解析配置文件,性能损耗
来源:https://ljd1996.github.io/2020/09/16/Android-SPI%E7%AC%94%E8%AE%B0/
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
springmvc图片上传及json数据转换过程详解
![](https://img.aspxhome.com/file/2023/7/74927_0s.png)
C#使用NPOI将List数据导出到Excel文档
![](https://img.aspxhome.com/file/2023/6/98176_0s.jpg)
Spring事务失效的一种原因关于this调用的问题
IDEA2020.1使用LeetCode插件运行并调试本地样例的方法详解
![](https://img.aspxhome.com/file/2023/0/81190_0s.png)
不用IDE写C#的Hello World的方法
![](https://img.aspxhome.com/file/2023/1/91281_0s.png)
Java While循环 do-while循环用法
Eclipse的Debug调试技巧大全(总结)
![](https://img.aspxhome.com/file/2023/3/59993_0s.png)
c#自带缓存使用方法 c#移除清理缓存
Java如何获取对象属性及对应值
c# 生成二维码的示例
![](https://img.aspxhome.com/file/2023/2/79082_0s.png)
四步五分钟Spring4快速集成Swagger
![](https://img.aspxhome.com/file/2023/4/89504_0s.png)
深入浅出讲解Java集合之Map接口
![](https://img.aspxhome.com/file/2023/6/62816_0s.png)
Springboot整合mybatis开启二级缓存的实现示例
![](https://img.aspxhome.com/file/2023/2/81372_0s.jpg)
C# 爬虫简单教程
![](https://img.aspxhome.com/file/2023/0/72470_0s.png)
CentOS 7系统下配置自定义JDK的教程
SpringMVC用JsonSerialize日期转换方法
![](https://img.aspxhome.com/file/2023/2/70332_0s.jpg)
浅谈关于Mybatis的mapper-locations配置问题
![](https://img.aspxhome.com/file/2023/5/67465_0s.jpg)
mybatis if传入字符串数字踩坑记录及解决
mybatis中映射文件(mapper)中的使用规则
![](https://img.aspxhome.com/file/2023/8/76248_0s.png)