了解Annotation-Processing-Tool

我知道APT(Annotation-Processing-Tool)还得从很有名的一个Android开源库ButterKnife说起。起初我对其并不在意,以为它内部的实现只是简单的反射,但是某一次当我看到一篇文章內这么说:

ButterKnife实现了编译期注入,效率远高于采用反射机制

于是便对这个编译期注入起了兴趣,通过一番学习总算是了解了APT这么个神奇的东西。

注:主要学习资料为官方文档

一些基本概念

注解处理器是 javac 自带的一个工具,用来在编译时期扫描处理注解信息。你可以为某些注解注册自己的注解处理器。

一个特定注解的处理器以 java 源代码(或者已编译的字节码)作为输入,然后生成一些文件(通常是.java文件)作为输出。那意味着什么呢?你可以生成 java 代码!这些 java 代码在生成的.java文件中。因此你不能改变已经存在的java类,例如添加一个方法。这些生成的 java 文件跟其他手动编写的 java 源代码一样,将会被 javac 编译。

AbstractProcessor

让我们来看一下处理器的 API。所有的处理器都继承了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
30
31
32
33
34
35
36
37
38
package com.example;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
public class MyProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annoations,
RoundEnvironment env) {
return false;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotataions.add("com.example.MyAnnotation");
return annotataions;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
}
  • init(ProcessingEnvironment processingEnv) :所有的注解处理器类都必须有一个无参构造函数。然而,有一个特殊的方法init(),它会被注解处理工具调用,以ProcessingEnvironment作为参数。ProcessingEnvironment 提供了一些实用的工具类Elements, TypesFiler。我们在后面将会使用到它们。

  • process(Set annoations, RoundEnvironment env) :这类似于每个处理器的main()方法。你可以在这个方法里面编码实现扫描,处理注解,生成 java 文件。使用RoundEnvironment 参数,你可以查询被特定注解标注的元素(原文:you can query for elements annotated with a certain annotation )。后面我们将会看到详细内容。

  • getSupportedAnnotationTypes():在这个方法里面你必须指定哪些注解应该被注解处理器注册。注意,它的返回值是一个String集合,包含了你的注解处理器想要处理的注解类型的全称。换句话说,你在这里定义你的注解处理器要处理哪些注解。

  • getSupportedSourceVersion() : 用来指定你使用的 java 版本。通常你应该返回SourceVersion.latestSupported() 。不过,如果你有足够的理由坚持用 java 6 的话,你也可以返回SourceVersion.RELEASE_6。我建议使用SourceVersion.latestSupported()。在 Java 7 中,你也可以使用注解的方式来替代重写getSupportedAnnotationTypes()getSupportedSourceVersion(),如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @SupportedSourceVersion(value=SourceVersion.RELEASE_7)
    @SupportedAnnotationTypes({
    // Set of full qullified annotation type names
    "com.example.MyAnnotation",
    "com.example.AnotherAnnotation"
    })
    public class MyProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annoations,
    RoundEnvironment env) {
    return false;
    }
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    }
    }

由于兼容性问题,特别是对于 android ,建议重写getSupportedAnnotationTypes()getSupportedSourceVersion() ,而不是使用 @SupportedAnnotationTypes@SupportedSourceVersion

注册处理器

在你的 .jar 文件中,你必须打包一个特殊的文件javax.annotation.processing.ProcessorMETA-INF/services目录下。因此你的 .jar 文件目录结构看起来就你这样:

1
2
3
4
5
6
7
MyProcess.jar
-com
-example
-MyProcess.class
-META-INF
-services
-javax.annotation.processing.Processor

javax.annotation.processing.Processor 文件的内容是一个列表,每一行是一个注解处理器的全称。例如:

1
2
com.example.MyProcess
com.example.AnotherProcess

亲身实践

这里我就不多加介绍了,我模仿ButterKnife写了一个小例子,感兴趣可以看看。