编译时注解项目实践之注解解析器

上篇我们详细介绍了注解,相信你已经对注解有了大概的了解,以及它可以给我们带来什么帮助,这篇文章我们继续实现 Extra 中的需求,我们已经定义好了注解,那我们怎么根据定义的注解帮我们自动生成获取数据的代码呢,这就是我们今天要讲得注解解析器。

先看下我们上篇文章定义的注解

1
2
3
4
5
6
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface ExtraParam {
//key in bundle
String value() default "";
}

自定义注解处理器

我们需要自定义一个注解处理类来处理我们注解,需继承自AbstractProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@AutoService(Processor.class)
public final class ExtraParamProcessor extends AbstractProcessor {
//空的构造函数 。编译期间,init()会自动被注解处理工具调用,并传入ProcessingEnviroment参数,通过该参数可以获取到很多有用的工具类: Elements , Types , Filer 等等
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
//Annotation Processor扫描出的结果会存储进roundEnv中,可以在这里获取到注解内容,编写你的操作逻辑。注意,process()函数中不能直接进行异常抛出,否则的话,运行Annotation Processor的进程会异常崩溃,然后弹出一大堆让人捉摸不清的堆栈调用日志显示.
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//在这个方法里处理你的逻辑
}
//该函数用于指定该自定义注解处理器(Annotation Processor)是注册给哪些注解的(Annotation),注解(Annotation)指定必须是完整的包名+类名(eg:com.example.MyAnnotation)
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(ExtraParam.class.getCanonicalName());
return annotations;
}
//用于指定你的java版本,一般返回:SourceVersion.latestSupported()。当然,你也可以指定具体java版本:
//return SourceVersion.RELEASE_7
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_7;
}
}

每个方法我都写了注释,这里需要补充一下的是

  1. getSupportedAnnotationTypes()和getSupportedSourceVersion()我们也可以通过注解申明

    1
    2
    3
    4
    5
    6
    @AutoService(Processor.class)
    @SupportedAnnotationTypes("me.loody.extra.annotation.ExtraParam")
    @SupportedSourceVersion(SourceVersion.RELEASE_7)
    public final class ExtraParamProcessor extends AbstractProcessor {
    }
  2. @AutoService 是为了向javac注册我们这个自定义的注解处理器,这样,在javac编译时,才会调用到我们这个自定义的注解处理器方法。AutoService这里主要是用来生成META-INF/services/javax.annotation.processing.Processor文件的。如果不加上这个注解,那么,你需要自己进行手动配置进行注册,目录结构如下图
    extra_processor

    javax.annotation.processing.Processor中定义至自定义注解处理类的路径即可

    1
    me.loody.extra.compiler.ExtraParamProcessor

注解处理器(APT)语法

这里我推荐一篇文章Android编译时注解框架-语法讲解,对APT语法做了详细的解释。这里我整理了下Element子类方法如下

Element 子类 含义 对应Target
ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。 @Target(ElementType.METHOD) @Target(ElementType.CONSTRUCTOR)
PackageElement 表示一个包程序元素。提供对有关包极其成员的信息访问。 @Target(ElementType.PACKAGE)
TypeElement 表示一个类或接口程序元素。提供对有关类型极其成员的信息访问 @Target(ElementType.TYPE)
TypeParameterElement 表示一般类、接口、方法或构造方法元素的类型参数。 @Target(ElementType.PARAMETER)
VariableElement 表示一个字段、enum常量、方法或构造方法参数、局部变量或异常参数。 @Target(ElementType.LOCAL_VARIABLE)

语法了解了,接下来只需要实现就好了,这里我主要说下思路

  1. 通过注解处理器解析出我们申明的变量,主要是类型,变量名
  2. 通过javapoet生成java代码,javapoet语法这里不做介绍,大家自行了解。

具体代码我已经上传到github,见ExtraParamProcessor

参考

Android编译时注解框架-语法讲解
javapoet