ServiceLoader
最近看滴滴开源的Dokit库源码(官网地址),发现Dokit使用了ServiceLoader和AutoService库实现的组件/插件机制,就研究了一下,感觉平时如果开发一个SDK框架应该用得上。
ServiceLoader可以理解为是一个接口或类的加载器,通过接口或抽象类能够找到实现该接口或抽象类的子类。
ServiceLoader的使用需要以下几个步骤:
- 创建一个接口文件
- 在resources资源目录下创建META-INF/services文件夹
- 在services文件夹中创建文件,以接口全名命名
- 创建接口实现类
这样使用比较麻烦,而google提供了一个库来简化这些操作,那就是AutoService库。
使用AutoService库
开源库地址:AutoService
1、添加该库依赖:
implementation 'com.google.auto.service:auto-service:1.0'
kapt 'com.google.auto.service:auto-service:1.0'
2、创建插件接口IPlugin
interface IPlugin {
void init();
}
3、创建抽象类AbsPlugin并实现IPlugin接口
abstract class AbsPlugin : IPlugin {
}
4、创建插件实现类,需要继承AbsPlugin抽象类和添加AutoService注解,注解中的参数就是定义的抽象类
@AutoService(AbsPlugin::class)
class LogPlugin : AbsPlugin() {
override fun init() {
Log.e("init: ", "LogPlugin")
}
}
再创建一个插件:
@AutoService(AbsPlugin::class)
class NetworkPlugin : AbsPlugin() {
override fun init() {
Log.e("init: ", "NetworkPlugin")
}
}
5、最后在MainActivity中使用ServiceLoader来加载抽象类,得到的是继承AbsPlugin的子类实例
// 得到的是继承AbsPlugin的子类实例
val list = ServiceLoader.load(AbsPlugin::class.java, javaClass.classLoader).toList();
list.forEach {
Log.e("onCreate: ", it.toString())
// 执行init方法
it.init();
}
打印结果:
E/onCreate:: com.learn.serviceloader.plugins.LogPlugin@db3bda9
E/init:: LogPlugin
E/onCreate:: com.learn.serviceloader.plugins.NetworkPlugin@3076f2e
E/init:: NetworkPlugin
最后的结果显示可以得到继承了AbsPlugin抽象类的所有子类的实例,然后直接调用该实例的成员方法即可。
ServiceLoader原理
在ServiceLoader.load的时候,根据传入的接口类,遍历META-INF/services目录下的以该类命名的文件中的所有类,并实例化返回,下面可以看一下ServiceLoader源码来验证一下:
1、load方法会new ServiceLoader类;
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
2、new ServiceLoader类的时候会调用reload方法;
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
reload();
}
3、reload方法中new LazyIterator()
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
4、LazyIterator表示懒加载的迭代器,这个迭代器会存储遍历好的类,可以使用next来获取迭代器中的值;
private class LazyIteratorimplements Iterator<S>{
private LazyIterator(Class<S> service, ClassLoader loader) {}
private boolean hasNextService() {}
private S nextService() {}
public boolean hasNext() {
return hasNextService();
}
public S next() {
return nextService();
}
public void remove() {}
}
5、重点看一下nextService和hasNextService方法中的实现;
源码中next方法调用nextService,nextService调用hasNextService
先看一下hasNextService源码:
private boolean hasNextService() {
//......
// 获取service资源文件路径
String fullName = PREFIX + service.getName();
if (loader == null)
// 加载资源文件
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
// ......
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
hasNextService源码中会获取资源文件的路径fullName,这里看一下拼接fullName的常量PREFIX:
private static final String PREFIX = "META-INF/services/";
所以最终路径应该是:META-INF/services/接口全类名路径文件名。
然后再看一下nextService源码:
private S nextService() {
if (!hasNextService())throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
// ......
// 反射创建类实例
c = Class.forName(cn, false, loader);
// ......
// 调用强转方法,将父类对象强制转换为子类对象
S p = service.cast(c.newInstance());
// 存储到LinkedHashMap中
providers.put(cn, p);
return p;
}
nextService方法中主要通过反射创建获取到的类实例,然后强转为子类对象,最后存储到LinkedHashMap中。
AutoService原理
AutoService处理注解,在使用的时候通过注解来收集子类信息,主要在processAnnotations方法中:
private void processAnnotations(
Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 获取AutoService注解的节点
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);
// 遍历节点
for (Element e : elements) {
TypeElement providerImplementer = MoreElements.asType(e);
AnnotationMirror annotationMirror = getAnnotationMirror(e, AutoService.class).get();
// 获取被注解的类
Set<DeclaredType> providerInterfaces = getValueFieldOfClasses(annotationMirror);
...
// 遍历类节点
for (DeclaredType providerInterface : providerInterfaces) {
TypeElement providerType = MoreTypes.asTypeElement(providerInterface);
if (checkImplementer(providerImplementer, providerType, annotationMirror)) {
// 存储类节点
providers.put(getBinaryName(providerType), getBinaryName(providerImplementer));
}
}
...
}
}
AutoService通过注解收集到被注解的子类,然后再来完成resources目录下创建META-INF/services接口文件的功能。
直接看源码AutoServiceProcessor.java类,在该类中的process方法中调用了processImpl方法,在该方法中又调用了generateConfigFiles()方法:
private void generateConfigFiles() {
// 获取文件操作对象
Filer filer = processingEnv.getFiler();
// 遍历接口
for (String providerInterface : providers.keySet()) {
// 获取META-INF/services/下的接口文件路径
String resourceFile = "META-INF/services/" + providerInterface;
SortedSet<String> allServices = Sets.newTreeSet();
// 获取编译输出路径
FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
Set<String> oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
allServices.addAll(oldServices);
Set<String> newServices = new HashSet<>(providers.get(providerInterface));
FileObject fileObject =filer.createResource(StandardLocation.CLASS_OUTPUT, "", resourceFile);
try (OutputStream out = fileObject.openOutputStream()) {
// 写入到编译输出目录
ServicesFiles.writeServiceFile(allServices, out);
}
}
}
}
AutoService也常用来做ARouter组件化库和BufferKnife注解处理来创建编译时文件。
本文介绍了如何使用ServiceLoader和AutoService实现组件/插件机制。ServiceLoader作为一个接口或类的加载器,通过指定接口或抽象类加载其实现类。然而,其使用过程较为繁琐,这时Google的AutoService库提供了简化操作的方式。文章详细解析了ServiceLoader的工作原理,并展示了AutoService的使用步骤,包括创建插件接口、抽象类以及添加注解。同时,分析了AutoService的原理,它是如何通过注解收集子类信息并在编译时生成配置文件。
5085

被折叠的 条评论
为什么被折叠?



