Java 注解处理器

APT(Annotation Processing Tool) 即注解处理器,是一种注解处理工具,用来在编译期扫描和处理注解,通过注解来生成 Java 文件。而且只能生成新的源文件而不能修改已经存在的源文件。

通过在编译期间调用 javac -processor 命令可以掉起注解处理器。

Java API 已经提供了扫描源码并解析注解的框架,开发者可以通过继承 AbstractProcessor 类并实现 process() 方法来实现自己的注解解析逻辑。

APT 的原理是在注解了某些代码元素(如字段、函数、类等)后,在编译时编译器会检查 AbstractProcessor 的子类,并且自动调用其 process() 方法,然后将添加了指定注解的所有代码元素作为参数传递给该方法,开发者再根据注解元素在编译期输出对应的 Java 代码。

注解

1
2
3
4
5
6
7
8
9
/**
* The common interface extended by all annotation types.
*/
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}

示例:

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD,ElementType.PARAMETER,ElementType.METHOD})
public @interface PrintMe {
String value() default "";
}

@Retention 定义注解的保留范围:

  • SOURCE:只作用于源码。可以用作 Lint、APT 这种工作在源码上的工具,会在编译环节被编译器主动舍弃,不会存在于 Class 文件中。
  • CLASS(默认):作用于编译后的 Class 文件中,但不会在运行时被 JVM 加载。因为在编译环节中生效,如 class 编译成 dex 的过程中,就可以通过 transform 等手段加以利用。
  • RUNTIME:作用于运行时,生命周期最长,可用于反射。

@Target 定义注解作用的对象:

  • FIELD:成员变量,包含枚举类型的成员值。
  • METHOD:在方法上是修饰方法的返回值。
  • PARAMETER:修饰方法的参数。
  • TYPE:修饰类或接口。

@Inhertited 用来表明注解的继承特性。注解本身并不具备继承性,这里所说的继承指的是注解所标记的类或接口的继承关系。
举个例子,注解@Foo 被定义为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Target(TYPE)
@Rentention(RUNTIME)
@Inhertited
public @interface Foo() {
int value() default -1;
}

@Foo(value = 2)
class A {}

class B extends A {}

public static void main(String[] args) {
Foo annotation = B.class.getAnnotation(Foo.class);
//如果去除 Foo 上的 @Inherited 注解,annotation == null
System.out.println("value: " + annotation.value());
}

编译时 API 介绍

Element 元素

每个 Element 代表的是一个编程节点,比如一个包、类或方法。是语言级别的数据结构的静态表示。

Element:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package javax.lang.model.element;

/**
* Represents a program element such as a package, class, or method.
* Each element represents a static, language-level construct
* (and not, for example, a runtime construct of the virtual machine).
*/
public interface Element extends javax.lang.model.AnnotatedConstruct {
// 转化为 TypeMirror
TypeMirror asType();

// 用以获取节点的具体类型
ElementKind getKind();

// 持外部使用访问者模式进行访问操作
<R, P> R accept(ElementVisitor<R, P> v, P p);

// 获取父节点
Element getEnclosingElement();

// 获取子节点
List<? extends Element> getEnclosedElements();
}

Element 的类型包括:注解、类、接口、构造器、枚举、枚举值、异常参数、变量、初始化块、本地变量、方法、包、参数、资源变量、静态初始化块、泛型参数等

PackageElement:

1
2
3
4
5
/**
* Represents a package program element. Provides access to information
* about the package and its members.
*/
public interface PackageElement extends Element, QualifiedNameable {}

表示一个包程序元素。提供对有关包及其成员的信息的访问。

ExecutableElement:

1
2
3
4
5
6
/**
* Represents a method, constructor, or initializer (static or
* instance) of a class or interface, including annotation type
* elements.
*/
public interface ExecutableElement extends Element, Parameterizable {}

表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。

VariableElement:

1
2
3
4
5
6
/**
* Represents a field, enum constant, method or constructor
* parameter, local variable, resource variable, or exception
* parameter.
*/
public interface VariableElement extends Element {}

表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。

TypeElement:

1
2
3
4
5
6
7
8
/**
* Represents a class or interface program element. Provides access
* to information about the type and its members. Note that an enum
* type is a kind of class and an annotation type is a kind of
* interface.
*/
public interface TypeElement extends Element,
Parameterizable, QualifiedNameable {}

表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意枚举类型是一种类,而注解类型是一种接口。

TypeParameterElement:

1
2
3
4
5
6
/**
* Represents a formal type parameter of a generic class, interface, method,
* or constructor element.
* A type parameter declares a {@link TypeVariable}.
*/
public interface TypeParameterElement extends Element {}

表示泛型类,接口,方法或构造函数元素的正式类型参数。 类型参数声明为 TypeVariable 。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.java.annotation;  // PackageElement

public class Circle { // TypeElement

private int i; // VariableElement
private Triangle triangle; // VariableElement

// ExecuteableElement
public Circle() {}

// ExecuteableElement
public void draw(String s) {// s: VariableElement
System.out.println(s);
}
}

Type 类型

位于 javax.lang.model.type 包下。

TypeMirror

TypeMirror 用来描述数据类型,包括基本类型、类和接口、数组、泛型、Null、通配符和 Void.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Represents a type in the Java programming language.
* Types include primitive types, declared types (class and interface types),
* array types, type variables, and the null type.
* Also represented are wildcard type arguments,
* the signature and return types of executables,
* and pseudo-types corresponding to packages and to the keyword {@code void}.
*/
public interface TypeMirror extends javax.lang.model.AnnotatedConstruct {
// 用以获取当前这个TypeMirror具体类型是什么
TypeKind getKind();
// 使用访问者模式进行类型转换后操作,其中R是Result结果类型,P是Parameter参数类型
<R, P> R accept(TypeVisitor<R, P> v, P p);
}

TypeMirror 有如下实现类:

  • ArrayType:代表数组类型,可通过 API 获取元数据类型;
  • DeclaredType:声明类型,即类或接口。可以通过 asElement()和后面的 Element 进行转换;
  • ExecutableType:方法、构造器、初始化块等可执行体的类型,类似反射中 Method,包含几个关键 API:
    1. List<? extends TypeVariable> getTypeVariables() 获取声明中的泛型,如 public <T> void foo(T t) 则会得到 T 的 TypeVariable;
    2. List<? extends TypeMirror> getParameterTypes()获取入参类型;
    3. TypeMirror getReturnType() 获取返回值类型;
    4. List<? extends TypeMirror> getThrownTypes() 获取声明的异常类型
  • TypeVariable:泛型类型,可以通过 getLowerBound()和 getUpperBound() 获取类型限定;
  • WildcardType:通配类型,API 同上;

与 TypeMirror 的区别是什么?

  • Element 是程序真正会运行到的节点,它可以具备注释
  • Set<String>是 TypeMirror,而 Set 是 Element。TypeMirror 是对 Element 提供了从类型到声明的一个映射,在更高的层次上对 Element 进行解释说明
  • 粗暴的解法:Element 是实例,可以透过它获得真正有效的数据。而 TypeMirror 是只是类型描述,无法代表实例的值。比如我们在某个 Annotation 中给了 value 是 1,透过 Element 可以得到 1,而透过 TypeMirror 只能得到是 Integer 类型

TypeVisitor

AbstractProcessor 类介绍

apt

Java Compilation Process with Annotation Processing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package javax.annotation.processing;

public interface Processor {
// 命令行选项
Set<String> getSupportedOptions();
// 支持的注解
Set<String> getSupportedAnnotationTypes();
// 支持的 Java 版本,一般返回 SourceVersion.latestSupported()
SourceVersion getSupportedSourceVersion();
// 注解处理器初始化
void init(ProcessingEnvironment processingEnv);
// 处理过程
boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv);

Iterable<? extends Completion> getCompletions(Element element,
AnnotationMirror annotation, ExecutableElement member, String userText);
}
  • ProcessingEnvironment: 代表了注解处理器框架提供的一个上下文环境,要创建新的代码或者向编译器输出信息或者获取其他工具类等都需要用到这个实例变量。
    1. getElementUtils():处理 Element 的工具类,用于获取程序的元素,例如包、类、方法。
    2. getTypeUtils():处理 TypeMirror 的工具类,用于取类信息
    3. getFiler():文件工具
    4. getMessager():编译过程中的日志组件
  • RoundEnvironment: 提供了访问到当前这个 Round 中语法树节点的功能。
    1. Set<? extends Element> getRootElements()上一轮所产生的根节点集合(类、接口等)
    2. Set<? extends Element> getElementsAnnotatedWith(TypeElement a) 通过指定的Annotation查找相关节点
    3. boolean processingOver() 如果返回true说明本轮结束后将不会再开启新一轮的处理,否则是false
    4. boolean errorRaised() 上一轮是否产生了错误
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package javax.annotation.processing;

public abstract class AbstractProcessor implements Processor {

protected ProcessingEnvironment processingEnv;
private boolean initialized = false;

protected AbstractProcessor() {}

// 设置注解处理器支持的参数
public Set<String> getSupportedOptions() {
SupportedOptions so = this.getClass().getAnnotation(SupportedOptions.class);
if (so == null)
return Collections.emptySet();
else
return arrayToSet(so.value());
}

// 可以通过注解的方式,设置注解处理器支持的注解
public Set<String> getSupportedAnnotationTypes() {
SupportedAnnotationTypes sat = this.getClass().getAnnotation(SupportedAnnotationTypes.class);
if (sat == null) {
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedAnnotationTypes annotation " + "found on " + this.getClass().getName() +
", returning an empty set.");
return Collections.emptySet();
}
else
return arrayToSet(sat.value());
}

// 设置注解处理器支持的 Java 版本。默认为JDK6
public SourceVersion getSupportedSourceVersion() {
SupportedSourceVersion ssv = this.getClass().getAnnotation(SupportedSourceVersion.class);
SourceVersion sv = null;
if (ssv == null) {
sv = SourceVersion.RELEASE_6;
if (isInitialized())
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
"No SupportedSourceVersion annotation " +
"found on " + this.getClass().getName() +
", returning " + sv + ".");
} else
sv = ssv.value();
return sv;
}

// 处理器初始化方法,只能初始化一次
public synchronized void init(ProcessingEnvironment processingEnv) {
if (initialized)
throw new IllegalStateException("Cannot call init more than once.");
Objects.requireNonNull(processingEnv, "Tool provided null ProcessingEnvironment");

this.processingEnv = processingEnv;
initialized = true;
}

// 处理过程
public abstract boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv);

public Iterable<? extends Completion> getCompletions(Element element,
AnnotationMirror annotation,
ExecutableElement member,
String userText) {
return Collections.emptyList();
}

protected synchronized boolean isInitialized() {
return initialized;
}

private static Set<String> arrayToSet(String[] array) {
assert array != null;
Set<String> set = new HashSet<String>(array.length);
for (String s : array)
set.add(s);
return Collections.unmodifiableSet(set);
}
}

process() 是抽象方法,用来实现注解处理器的具体逻辑。比如获取被注解的元素,生成 Java 源代码等信息。
返回值表示注解是否由当前 Processor 处理。如果返回 true,则这些注解由此注解来处理,后续其它的 Processor 无需再处理它们;如果返回 false,则这些注解未在此 Processor 中处理,那么后续 Processor 可以继续处理它们。
因为存在另一种只要消费掉所有注解后也可停止收集阶段的设计,所以往往这里就是返回 false,Google 的 auto-services 也是如此,并不会影响最终的使用。

1
2
3
4
5
6
7
8
9
@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
Set<? extends Element> elements = env.getElementsAnnotatedWith(PrintMe.class);
//由于编译器的输出无法打印到控制台,使用 javapoet 库把需要输出的信息写入到一个新的类中
for (Element element : elements) {
PrintMe annotation = element.getAnnotation(PrintMe.class);
...
}
return false;
}

注册注解处理器

  1. 在 META-INF/services 文件夹下创建 javax.annotation.processing.Processor 文件,在此文件中输入实现注解处理器的实体类的全名。

  2. 使用 google 的 AutoService 库:

    1
    2
    3
    4
    dependencies {
    implementation 'com.google.auto.service:auto-service:1.0'
    annotationProcessor 'com.google.auto.service:auto-service:1.0'
    }

相关库

JavaPoet

JavaPoet 是 square 开源的 Java 代码生成框架,可以很方便地通过其提供的 API 来生成指定格式(修饰符、返回值、参数、函数体等)的代码。

依赖: com.squareup:javapoet:1.11.1

auto-service

auto-service 是由 Google 开源的注解处理器。

依赖: com.google.auto.service:auto-service:1.0

参考

[1] Processor - oracle docs
[2] Annotation Types - - oracle docs
[3] 深入理解 Java 注解处理器 APT
[4] 使用 Google 开源库 AutoService 进行组件化开发