使用Google开源库AutoService进行组件化开发

本文详细介绍了Java编译过程中Javac的处理步骤,特别是AutoService注解的实现机制,包括注解处理器的参与、抽象语法树的构建、符号表填充以及注解处理流程。文章重点讲解了如何在编译时检查和处理@AutoService注解的应用条件。
摘要由CSDN通过智能技术生成

}

使用就是这么几个步骤,比较简单,下面看下AutoService实现原理。

2.实现原理

首先先简单介绍下Javac的编译过程,大致可以分为3个过程:

  • 解析与填充符号表
  • 插入式注解处理器的注解处理过程
  • 分析与字节码生成过程

看下一个图片,图片来源深入理解Java虚拟机,首先会进行词法和语法分析,词法分析将源代码的字符流转变为Token集合,关键字/变量名/字面量/运算符读可以成为Token,词法分析过程由com.sun.tools.javac.parserScanner类实现;

语法分析是根据Token序列构造抽象语法树的过程,抽象语法树AST是一种用来描述程序代码语法结构的树形表示,语法树的每一个节点读代表着程序代码中的一个语法结构,例如包/类型/修饰符/运算符/接口/返回值/代码注释等,在javac的源码中,语法分析是由com.sun.tools.javac.parser.Parser类实现,这个阶段产出的抽象语法树由com.sun.tools.javac.tree.JCTree类表示。经过上面两个步骤编译器就基本不会再对源码文件进行操作了,后续的操作读建立在抽象语法树上。

完成了语法和词法分析后就是填充符号表的过程。符号表是由一组符号地址和符号信息构成的表格。填充符号表的过程由com.sun.tools.javac.comp.Enter类实现。

如前面介绍的,如果注解处理器在处理注解期间对语法树进行了修改,编译器将回到解析与填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个Round,如下图中的环。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上面简单回顾了下编译注解的一些东西,接下来看下AutoService这个注解的实现,使用它有三个限定条件;

  • 不能是内部类和匿名类,必须要有确定的名称
  • 必须要有公共的,可调用的无参构造函数
  • 使用这个注解的类必须要实现value参数定义的接口

@Documented
@Target(TYPE)
public @interface AutoService {
/** Returns the interface implemented by this service provider. */
Class<?> value();
}

有注解,必须要有对应的注解处理器,AutoServiceProcessor继承AbstractProcessor,一般我们会实现其中的3个方法, 在getSupportedAnnotationTypes中返回了支持的注解类型AutoService.class;getSupportedSourceVersion ,用来指定支持的java版本,一般来说我们都是支持到最新版本,因此直接返回 SourceVersion.latestSupported()即可;主要还是process方法。

public class AutoServiceProcessor extends AbstractProcessor {

@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

@Override
public Set getSupportedAnnotationTypes() {
return ImmutableSet.of(AutoService.class.getName());
}

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
try {
return processImpl(annotations, roundEnv);
} catch (Exception e) {
// We don’t allow exceptions of any kind to propagate to the compiler
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
fatalError(writer.toString());
return true;
}
}
}

process方法调用processImpl,接着看下这个方法的实现,先看下方法实现,就两个逻辑判断,如果上一次循环中注解处理器已经处理完了,就调用generateConfigFiles生成MEATA_INF配置文件;如果上一轮没有处理就调用processAnnotations处理注解。返回true就代表改变或者生成语法树中的内容;返回false就是没有修改或者生成,通知编译器这个Round中的代码未发生变化。

private boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
generateConfigFiles();
} else {
processAnnotations(annotations, roundEnv);
}

return true;
}

再接着往下看代码之前先看下两个环境变量,RoundEnvironmentProcessingEnvironment

RoundEnvironment提供了访问到当前这个Round中语法树节点的功能,每个语法树节点在这里表示为一个Element,在javax.lang.model包中定义了16类Element,包括常用的元素:包,枚举,类,注解,接口,枚举值,字段,参数,本地变量,异常,方法,构造函数,静态语句块即static{}块,实例语句块即{}块,参数化类型即反省尖括号内的类型,还有未定义的其他语法树节点。

public enum ElementKind {
PACKAGE,
ENUM,
CLASS,
ANNOTATION_TYPE,
INTERFACE,
ENUM_CONSTANT,
FIELD,
PARAMETER,
LOCAL_VARIABLE,
EXCEPTION_PARAMETER,
METHOD,
CONSTRUCTOR,
STATIC_INIT,
INSTANCE_INIT,
TYPE_PARAMETER,
OTHER,
RESOURCE_VARIABLE;

private ElementKind() {
}

public boolean isClass() {
return this == CLASS || this == ENUM;
}

public boolean isInterface() {
return this == INTERFACE || this == ANNOTATION_TYPE;
}

public boolean isField() {
return this == FIELD || this == ENUM_CONSTANT;
}
}

看下RoundEnvironment的源码,errorRaised方法返回上一轮注解处理器是否产生错误;getRootElements返回上一轮注解处理器生成的根元素;最后两个方法返回包含指定注解类型的元素的集合,画重点,这个就是我们自定义注解处理器需要经常打交道的方法。

public interface RoundEnvironment {
boolean processingOver();

boolean errorRaised();

Set<? extends Element> getRootElements();

Set<? extends Element> getElementsAnnotatedWith(TypeElement var1);

Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> var1);
}

另外一个参数ProcessingEnvironment,在注解处理器初始化的时候(init()方法执行的时候)创建,代表了注解处理器框架提供的一个上下文环境,要创建新的代码或者向编译器输出信息或者获取其他工具类等都需要用到这个实例变量。看下它的源码。

  • Messager用来报告错误,警告和其他提示信息;
  • Filer用来创建新的源文件,class文件以及辅助文件;
  • Elements中包含用于操作Element的工具方法;
  • Types中包含用于操作类型TypeMirror的工具方法;

public interface ProcessingEnvironment {
Map<String, String> getOptions();

Messager getMessager();

Filer getFiler();

Elements getElementUtils();

Types getTypeUtils();

SourceVersion getSourceVersion();

Locale getLocale();
}

介绍完一些基础变量后,我们就接着上面先看下processAnnotations方法,方法看起来有点长,但是结构很简单,首先第一步通过RoundEnvironmentgetElementsAnnotatedWith(AutoService.class)拿到所有的标注了AutoService注解的元素。

private void processAnnotations(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {

// 1.
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(AutoService.class);

log(annotations.toString());
log(elements.toString());

for (Element e : elements) {
// TODO(gak): check for error trees?
// 2.
TypeElement providerImplementer = (TypeElement) e;
// 3.
AnnotationMirror providerAnnotation = getAnnotationMirror(e, AutoService.class).get();
// 4.
DeclaredType providerInterface = getProviderInterface(providerAnnotation);
TypeElement providerType = (TypeElement) providerInterface.asElement();

log("provider interface: " + providerType.getQualifiedName());
log("provider implementer: " + providerImplementer.getQualifiedName());

// 5.
if (!checkImplementer(providerImplementer, providerType)) {
String message = "ServiceProviders must implement their service provider interface. "

  • providerImplementer.getQualifiedName() + " does not implement "
  • providerType.getQualifiedName();
    error(message, e, providerAnnotation);
    }

// 6.
String providerTypeName = getBinaryName(providerType);
String providerImplementerName = getBinaryName(providerImplementer);
log("provider interface binary name: " + providerTypeName);
log("provider implementer binary name: " + providerImplementerName);

providers.put(providerTypeName, providerImplementerName);
}
}

public static Optional getAnnotationMirror(Element element,
Class<? extends Annotation> annotationClass) {
String annotationClassName = annotationClass.getCanonicalName();
for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
TypeElement annotationTypeElement = asType(annotationMirror.getAnnotationType().asElement());
if (annotationTypeElement.getQualifiedName().contentEquals(annotationClassName)) {
return Optional.of(annotationMirror);
}
}
return Optional.absent();
}

AutoService只能作用于非内部非匿名类或者接口,第二步在for循环中强转Element为TypeElement,这个就是被AutoService标注的元素,这里简称为T。接下来这个可能让人容易乱,在前面说过每一个javac是一个循环过程,在第一次扫描到AutoService注解的时候是还没有T的class对象,所以也就不能通过反射来拿到这个注解和注解的参数值value。这个时候第三步就得通过AnnotationMirror,用来表示一个注解,通过它可以拿到注解类型和注解参数。在getAnnotationMirror会判断这个T的注解(通过element.getAnnotationMirrors())名称是不是等于AutoService,相等就返回这个AutoServiceAnnotationMirror

public interface AnnotationMirror {
DeclaredType getAnnotationType();

Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues();
}

拿到这个注解了,接下来就是要拿到注解的参数value值了,这个在第四步getProviderInterface方法中完成。

private DeclaredType getProviderInterface(AnnotationMirror providerAnnotation) {

Map<? extends ExecutableElement, ? extends AnnotationValue> valueIndex =
providerAnnotation.getElementValues();
log("annotation values: " + valueIndex);

AnnotationValue value = valueIndex.values().iterator().next();
return (DeclaredType) value.getValue();
}

这里也是同上面的原因,在这个阶段我们不可能通过下面的代码反射来拿到注解的参数值,因为这个时候还拿不到class对象。所以上面费了很大的劲去通过AnnotationMirror来拿到注解的参数值,在我们这个栗子中就是Display.class了。

AutoService autoservice = e.getAnnotation(AutoService.class);
Class<?> providerInterface = autoservice.value()

接下来第5步检查类型T是不是实现了注解参数值说明的接口,也就是ADisplayBDisplay是不是实现了Display接口,没有实现肯定就是没有意义了。第6步就是获取到接口名和实现类名,注册到map中,类似于Map<Display, [ADisplay, BDisplay]>这种形式,即key是接口名,value是实现了接口也就是注解AutoService标注的实现类。

通过上面的步骤就已经扫描得到了所有的通过AutoService标注的实现类和对应接口的映射关系,并且在processImpl里面返回了true,下个Round就是生成配置文件了。看下processImpl if分支里面的generateConfigFiles方法。

private void generateConfigFiles() {
Filer filer = processingEnv.getFiler();

// 1.
for (String providerInterface : providers.keySet()) {
String resourceFile = “META-INF/services/” + providerInterface;
log("Working on resource file: " + resourceFile);
try {
SortedSet allServices = Sets.newTreeSet();
try {
// 2.
FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, “”,
resourceFile);
log("Looking for existing resource file at " + existingFile.toUri());
// 3.
Set oldServices = ServicesFiles.readServiceFile(existingFile.openInputStream());
log("Existing service entries: " + oldServices);
allServices.addAll(oldServices);
} catch (IOException e) {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

文末

架构师不是天生的,是在项目中磨练起来的,所以,我们学了技术就需要结合项目进行实战训练,那么在Android里面最常用的架构无外乎 MVC,MVP,MVVM,但是这些思想如果和模块化,层次化,组件化混和在一起,那就不是一件那么简单的事了,我们需要一个真正身经百战的架构师才能讲解透彻其中蕴含的深理。

移动架构师

系统学习技术大纲

一线互联网Android面试题总结含详解(初级到高级专题)

image

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

组件化混和在一起,那就不是一件那么简单的事了,我们需要一个真正身经百战的架构师才能讲解透彻其中蕴含的深理。

[外链图片转存中…(img-34DuIzSU-1712373438322)]

[外链图片转存中…(img-nKxisfAB-1712373438322)]

一线互联网Android面试题总结含详解(初级到高级专题)

[外链图片转存中…(img-TfBi7tJ6-1712373438323)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的autoservice注解是一种用于服务发现的机制,它能够自动将服务的实现类注册到指定的配置文件中,一般是META-INF/services目录下的文件。 然而,的确有一些情况下autoservice注解无法生成META-INF文件。这可能是由于以下几种原因导致的: 1. 编译问题:可能是由于编译配置的问题,导致编译器无法正确处理autoservice注解。这种情况下,我们可以尝试检查编译器的配置,或者使用其他IDE或编译工具进行尝试。 2. 或框架限制:有些或框架可能不支持autoservice注解,或者在使用autoservice注解时会有特殊的要求。我们可以查看相关或框架的文档,了解其对autoservice注解的支持情况,或者尝试其他方式实现服务发现。 3. 配置文件缺失或错误:autoservice注解生成META-INF文件的前提是META-INF目录存在,并且配置文件的名称和路径正确。如果自动生成的META-INF文件不存在,或者文件名或路径有误,就无法实现服务发现。我们需要检查项目的文件结构,确认META-INF目录是否存在,并且配置文件的名称和路径是否正确。 总之,虽然autoservice注解通常可以自动将服务实现类注册到META-INF文件中,但在某些情况下可能会遇到无法生成META-INF文件的问题。我们需要仔细检查编译配置、或框架限制,以及配置文件是否正确等因素,以找出问题所在,并采取相应的解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值