2024年Go最新使用Google开源库AutoService进行组件化开发(2),714页PDF的鸿蒙学习笔记

img
img
img

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

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

首先先简单介绍下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()) {

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

[外链图片转存中…(img-1ZoJmJDg-1715386181537)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值