注解处理在 Java 1.5 的时候就已经发布了,虽然它很老了,但是却是最强大的 API 之一。下面我们会先讨论注解处理,代码自动生成以及使用到这些技术的开源库。
什么是注解
实际上,我们应该都知道什么是注解。我们经常使用到的:@Override
,@Singleton
,@StringRes
等等,这些就是注解。
注解是一种可以添加到Java源代码的语法元数据。 我们可以注释类,接口,方法,变量,参数等。 可以从源文件中读取Java注解。 Java注解也可以嵌入和读取编译器生成的类文件。 Java VM可以在运行时保留注解,并通过反射进行读取。
比如:
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
创建一个注解需要两部分信息: Retention 和 Target 。
RetentionPolicy 指定了注解应该保留到程序生命周期的什么时候。举个例子:注解可以保留到程序的编译时期或者运行时期。
ElementTypes 指定了注解应该作用于程序的哪一个部分。有3个取值:
- SOURCE —— 编译时期,不会储存
- CLASS —— 储存在 class 文件中,但是不会保留到运行时期
- RUNTIME —— 储存在 class 文件中,运行时期可以访问(通过反射)
拿 BindView
注解来说,RetentionPolicy.SOURCE
表示注解只需要在编译时期保存,之后就不需要了。ElementType.FIELD
表示该注解只能修饰字段。
注解处理器介绍
编译时期
Annotation Processor 实际上是 javac 编译器的一部分,所以注解处理时发生在编译时期,这有许多好处,其中之一就是“在编译其实发生错误比运行时期发生错误要好的多”。
无反射
Java 的反射 API 会在运行时抛出许多错误,这实在是有点蛋疼。但是 Annotation Processor 就不一样了,它会直接给我们一个程序的语义结构,我们使用这个语义结构就可以分析注解所处的上下文场景,然后做处理。
生成样板代码
Annotation Processor 最大的用处就是用来生成样板代码了,比如著名的 ButterKnife 等开源库。
注意:注解处理器只能生成新的文件,无法更改已经存在的文件。
注解处理器是如何工作的
注解处理会执行很多轮。编译器首先会读取java源文件,然后查看文件中是否有使用注解,如果有使用,则调用其对应的注释处理器,这个注解处理器(可能会)生成新的带有注解的java源文件。这些新注解将再次调用其相应的注释处理器,然后再次生成更多的java源文件。就这样一直循环,直到没有新的文件生成。
注册注解处理器
java 编译器需要知道所有的注解处理器,所以如果我们想要自定义一个处理器,我们必须要让 java 编译器知道我们创建了一个。
有两种方法来注册一个处理器:
-
老方法:
创建一个目录:
<your-annotation-processor-module>/src/main/resources/META-INF/services
然后在services文件夹里面,创建一个名字叫做
javax.annotation.processing.Processor
的文件。在这个文件中声明你的处理器的权限定名:<your-package>.YourProcessor
-
新方法:
使用谷歌的 AutoService 库。
package foo.bar; import javax.annotation.processing.Processor; @AutoService(Processor.class) final class MyProcessor implements Processor { // … }
注意在 gradle 文件中引入依赖。
创建一个注解处理器
首先,我们需要继承一个父类:
public class Processor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
// initialize helper/utility classes...