深入理解JVM(七)——插件化注解处理器

Java 中的注解(Annotation)在JDK 1.5中第一次被引入,当时开发者只能在运行时通过反射机制获取。反射的效率不高,局限性大,因此在JDK 1.6的JSR 269规范中引入了编译期注解,即插件化注解处理器(Pluggable Annotation Processor),使得我们可以通过注解处理器在Javac的编译阶段修改抽象语法树的内部。

在了解插件化注解处理器之前,先来了解一下Javac的编译过程以及抽象语法树操作相关的API。

Javac编译流程

Java中通过javac编译器将源码.java文件编译为.class文件,从编译原理的角度来说这一过程属于前端编译。

整个编译过程分为七个阶段,其中process过程将会使用注解处理器来处理源码中的注解,如下图所示。

javac编译流程

编译原理并不是本文的重点,所以下面只是简单介绍这七个步骤,具体细节可以翻看javac源码,它本身也是由Java语言编写的。

  1. parse阶段:读取.java文件并做词法分析和语法分析。
  2. enter阶段:解析和填充符号表。
  3. process阶段:处理编译时注解。
  4. attr阶段:检查语义合法性、常量折叠。
  5. flow阶段:处理数据流分析。
  6. desugar阶段:去除高版本JDK中的语法糖。
  7. generate:生成字节码并输出到.class文件。

抽象语法树操作API

抽象语法树操作的核心类是NamesJCTreeTreeMaker,其中Names类提供了访问标识符的方法,JCTree类是语法树元素的基类,TreeMaker类封装了创建语法树节点的办法。

Names

Names类提供了访问标识符Name的方法,它最常用的方法是fromString,用来从一个字符串中获取Name对象,此方法声明如下:

1
2
3
public Name fromString(String s) {
return table.fromString(s);
}

JCTree

JCTree是语法树元素的基类,包含一个重要的字段pos,该字段用于指明当前语法树节点(JCTree)在语法树中的位置,因此我们不能直接用new关键字来创建语法树节点,即使创建了也没有意义。此外,结合访问者模式,将数据结构与数据的处理进行解耦,部分源码如下:

1
2
3
4
5
6
7
8
9
10
public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {

public int pos = -1;

...

public abstract void accept(JCTree.Visitor visitor);

...
}

我们可以看到JCTree是一个抽象类,这里重点介绍几个JCTree的子类:

  1. JCStatement:声明语法树节点,常见的子类如下
    • JCBlock:语句块语法树节点
    • JCReturn:return语句语法树节点
    • JCClassDecl:类定义语法树节点
    • JCVariableDecl:字段/变量定义语法树节点
  2. JCMethodDecl:方法定义语法树节点
  3. JCModifiers:访问标志语法树节点
  4. JCExpression:表达式语法树节点,常见的子类如下
    • JCAssign:赋值语句语法树节点
    • JCIdent:标识符语法树节点,可以是变量,类型,关键字等等

TreeMaker

TreeMaker用于创建一系列的语法树节点,我们上面说了创建JCTree不能直接使用new关键字来创建,所以Java为我们提供了一个工具,就是TreeMaker,它会在创建时为我们创建的JCTree对象设置pos字段,所以必须使用上下文相关的TreeMaker对象来创建语法树节点。

具体的API介绍可以参照,TreeMakerAPI,接下来着重介绍一下常用的几个方法。

TreeMaker.Modifiers

TreeMaker.Modifiers方法用于创建访问标志语法树节点(JCModifiers),源码如下:

1
2
3
4
5
6
7
8
9
10
11
public JCModifiers Modifiers(long flags) {
return Modifiers(flags, List.nil());
}

public JCModifiers Modifiers(long flags,
List<JCAnnotation> annotations) {
JCModifiers tree = new JCModifiers(flags, annotations);
boolean noFlags = (flags & (Flags.ModifierFlags | Flags.ANNOTATION)) == 0;
tree.pos = (noFlags && annotations.isEmpty()) ? Position.NOPOS : pos;
return tree;
}

其中参数flag表示访问标志,annotations表示注解列表。

flag可以使用枚举类com.sun.tools.javac.code.Flags来表示,例如要表示public static final可以这样用:

1
treeMaker.Modifiers(Flags.PUBLIC + Flags.STATIC + Flags.FINAL);

TreeMaker.ClassDef

TreeMaker.ClassDef用于创建类定义语法树节点(JCClassDecl),源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public JCClassDecl ClassDef(JCModifiers mods,
Name name,
List<JCTypeParameter> typarams,
JCExpression extending,
List<JCExpression> implementing,
List<JCTree> defs) {
JCClassDecl tree = new JCClassDecl(mods,
name,
typarams,
extending,
implementing,
defs,
null);
tree.pos = pos;
return tree;
}

参数说明:

  1. mods:访问标志,可以通过方法TreeMaker.Modifiers来创建
  2. name:类名
  3. typarams:泛型参数列表
  4. extending:父类
  5. implementing:实现的接口
  6. defs:类定义的详细语句,包括字段、方法的定义等等

TreeMaker.MethodDef

TreeMaker.MethodDef用于创建方法定义语法树节点(JCMethodDecl),源码如下:

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
public JCMethodDecl MethodDef(JCModifiers mods,
Name name,
JCExpression restype,
List<JCTypeParameter> typarams,
List<JCVariableDecl> params,
List<JCExpression> thrown,
JCBlock body,
JCExpression defaultValue) {
JCMethodDecl tree = new JCMethodDecl(mods,
name,
restype,
typarams,
params,
thrown,
body,
defaultValue,
null);
tree.pos = pos;
return tree;
}

public JCMethodDecl MethodDef(MethodSymbol m,
Type mtype,
JCBlock body) {
return (JCMethodDecl)
new JCMethodDecl(
Modifiers(m.flags(), Annotations(m.getAnnotationMirrors())),
m.name,
Type(mtype.getReturnType()),
TypeParams(mtype.getTypeArguments()),
Params(mtype.getParameterTypes(), m),
Types(mtype.getThrownTypes()),
body,
null,
m).setPos(pos).setType(mtype);
}

参数说明:

  1. mods:访问标志
  2. name:方法名
  3. restype:返回类型
  4. typarams:泛型参数列表
  5. params:参数列表
  6. thrown:异常声明列表
  7. body:方法体
  8. defaultValue:默认方法(可能是interface中的哪个default)
  9. m:方法符号
  10. mtype:方法类型。包含多种类型,泛型参数类型、方法参数类型、异常参数类型、返回参数类型。

其中返回类型restype填写null或者treeMaker.TypeIdent(TypeTag.VOID)都代表返回void类型。

TreeMaker.VarDef

TreeMaker.VarDef用于创建字段/变量定义语法树节点(JCVariableDecl),源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public JCVariableDecl VarDef(JCModifiers mods,
Name name,
JCExpression vartype,
JCExpression init) {
JCVariableDecl tree = new JCVariableDecl(mods, name, vartype, init, null);
tree.pos = pos;
return tree;
}

public JCVariableDecl VarDef(VarSymbol v,
JCExpression init) {
return (JCVariableDecl)
new JCVariableDecl(
Modifiers(v.flags(), Annotations(v.getAnnotationMirrors())),
v.name,
Type(v.type),
init,
v).setPos(pos).setType(v.type);
}

参数说明:

  1. mods:访问标志
  2. name:参数名称
  3. vartype:类型
  4. init:初始化语句
  5. v:变量符号

TreeMaker.Ident

TreeMaker.Ident用于创建标识符语法树节点(JCIdent),源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public JCIdent Ident(Name name) {
JCIdent tree = new JCIdent(name, null);
tree.pos = pos;
return tree;
}

public JCIdent Ident(Symbol sym) {
return (JCIdent)new JCIdent((sym.name != names.empty)
? sym.name
: sym.flatName(), sym)
.setPos(pos)
.setType(sym.type);
}

public JCExpression Ident(JCVariableDecl param) {
return Ident(param.sym);
}

TreeMaker.Return

TreeMaker.Return用于创建return语句(JCReturn),源码如下:

1
2
3
4
5
public JCReturn Return(JCExpression expr) {
JCReturn tree = new JCReturn(expr);
tree.pos = pos;
return tree;
}

TreeMaker.Select

TreeMaker.Select用于创建域访问/方法访问(这里的方法访问只是取到名字,方法的调用需要用TreeMaker.Apply)语法树节点(JCFieldAccess),源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public JCFieldAccess Select(JCExpression selected,
Name selector)
{
JCFieldAccess tree = new JCFieldAccess(selected, selector, null);
tree.pos = pos;
return tree;
}

public JCExpression Select(JCExpression base,
Symbol sym) {
return new JCFieldAccess(base, sym.name, sym).setPos(pos).setType(sym.type);
}

参数说明:

  1. selected:.运算符左边的表达式
  2. selector:.运算符右边的表达式

举个例子,使用如下代码可以生成this.name语句:

1
TreeMaker.Select(treeMaker.Ident(names.fromString("this")), names.fromString("name"));

TreeMaker.NewClass

TreeMaker.NewClass用于创建new语句语法树节点(JCNewClass),源码如下:

1
2
3
4
5
6
7
8
9
public JCNewClass NewClass(JCExpression encl,
List<JCExpression> typeargs,
JCExpression clazz,
List<JCExpression> args,
JCClassDecl def) {
JCNewClass tree = new JCNewClass(encl, typeargs, clazz, args, def);
tree.pos = pos;
return tree;
}

参数说明:

  1. encl:以表达式的方式创建,正常new传null即可
  2. typeargs:参数类型列表
  3. clazz:待创建对象的类型
  4. args:参数列表
  5. def:类定义

TreeMaker.Apply

TreeMaker.Apply用于创建方法调用语法树节点(JCMethodInvocation),源码如下:

1
2
3
4
5
6
7
public JCMethodInvocation Apply(List<JCExpression> typeargs,
JCExpression fn,
List<JCExpression> args) {
JCMethodInvocation tree = new JCMethodInvocation(typeargs, fn, args);
tree.pos = pos;
return tree;
}

参数说明:

  1. typeargs:参数类型列表
  2. fn:调用语句
  3. args:参数列表

TreeMaker.Assign

TreeMaker.Assign用户创建赋值语句语法树节点(JCAssign),源码如下:

1
2
3
4
5
6
public JCAssign Assign(JCExpression lhs,
JCExpression rhs) {
JCAssign tree = new JCAssign(lhs, rhs);
tree.pos = pos;
return tree;
}

参数说明:

  1. lhs:赋值语句左边表达式
  2. rhs:赋值语句右边表达式

TreeMaker.Exec

TreeMaker.Exec用于创建可执行语句语法树节点(JCExpressionStatement),源码如下:

1
2
3
4
5
public JCExpressionStatement Exec(JCExpression expr) {
JCExpressionStatement tree = new JCExpressionStatement(expr);
tree.pos = pos;
return tree;
}

TreeMaker.Apply以及TreeMaker.Assign需要外面包一层TreeMaker.Exec来获得一个JCExpressionStatement。

TreeMaker.Block

TreeMaker.Block用于创建组合语句的语法树节点(JCBlock),源码如下:

1
2
3
4
5
6
public JCBlock Block(long flags,
List<JCStatement> stats) {
JCBlock tree = new JCBlock(flags, stats);
tree.pos = pos;
return tree;
}

参数说明:

  1. flags:访问标志
  2. stats:语句列表

常用类介绍

上节讨论了操作抽象语法树的核心API,这些API可以帮助我们在javac的编译阶段修改源码,但实际上大部分情况下我们实现注解处理器并不需要去修改源码,而是通过生成辅助类来优化开发效率。

因此大部分情况下我们仅需要使用一些注解处理器中常用的类就可以实现我们需要的功能,下面就来简单介绍这些类。

AnnotationMirror

AnnotationMirror用于表示一个注解,其中提供了两个方法分别用于获取注解类型和注解的值,如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
public interface AnnotationMirror {

/**
* 获取此注解的ElementType
*/
DeclaredType getAnnotationType();

/**
* 获取此注解的所有Element的值
*/
Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues();
}

Element

Element我们通常称为节点或者元素。例如HTML作为一种结构体语言,其中有很多规范的标签限定,每种标签标示一个Element,如下代码:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Sukai's Blog</title>
</head>
<body>
<div>...</div>
</body>
</html>

对于Java源文件来说,同样也是一种结构体语言,源代码的每一个部分都是一个特定类型的Element,也就是说Element代表源文件中的元素,例如包、类、字段、方法等。

Element类是一个接口,由Element衍生出来的扩展类共有五种,分别是:

  • PackageElement:表示一个包程序元素,提供对有关包及成员的信息的访问。
  • TypeElement:表示一个类或者接口程序元素,提供对有关类型及其成员信息的访问。
  • TypeParameterElement:表示一个泛型元素。
  • VariableElement:表示一个字段、enum常量、方法或者构造方法的参数、局部变量或异常参数。
  • ExecuteableElement:表示某个类或者接口的方法、构造方法或初始化程序(静态或者实例)。

Element类中定义了几个方法,我们来看一下:

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
public interface Element extends javax.lang.model.AnnotatedConstruct {
/**
* 获取此元素的实际类型
*/
TypeMirror asType();

/**
* 获取此元素的元素类型
*/
ElementKind getKind();

/**
* 获取此元素的修饰符,
*/
Set<Modifier> getModifiers();

/**
* 获取不带包名的simpleName
*/
Name getSimpleName();

/**
* 获取直接父节点Element
*/
Element getEnclosingElement();

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

@Override
boolean equals(Object obj);

@Override
int hashCode();


/**
* 获取此节点元素的注解,不会包含子节点的注解
* @since 1.6
*/
@Override
List<? extends AnnotationMirror> getAnnotationMirrors();

/**
* 获取特定的注解
* @since 1.6
*/
@Override
<A extends Annotation> A getAnnotation(Class<A> annotationType);

/**
* 在此节点上应用Visitor,可以修改元素节点树
*/
<R, P> R accept(ElementVisitor<R, P> v, P p);
}

ElementKind

ElementKind是一个枚举类,其中包含了所有Element类型,下表为ElementKind枚举类中的所有常量,详细信息请查看官方文档。

类型 说明
PACKAGE 包类型
ENUM 枚举类型
CLASS 普通类类型
ANNOTATION_TYPE 注解类型
INTERFACE 普通接口类型
ENUM_CONSTANT 枚举常量类型
FIELD 类中定义的字段类型
PARAMETER 方法或构造器中的参数类型
LOCAL_VARIABLE 局部变量类型
EXCEPTION_PARAMETER 异常处理参数类型
METHOD 方法类型
CONSTRUCTOR 构造器类型
STATIC_INIT 静态代码块类型
INSTANCE_INIT 非静态代码块类型
TYPE_PARAMETER 泛型参数类型
RESOURCE_VARIABLE 资源变量类型
OTHER 其他类型

如果我们要判断一个元素的类型,应该使用Element.getKind()方法配合ElementKind枚举类进行判断。

尽量避免使用instanceof进行判断,因为比如TypeElement既表示类又表示一个接口,这样判断的结果可能不是你想要的。例如我们判断一个元素是不是一个类:

1
if (element is TypeElement)

这种情况element有可能是class、interface或者是enum,所以我们应该使用ElementKind来判断:

1
if (element.kind == ElementKind.CLASS)

Elements

Elements是JDK 1.6中加入的协助操作Element的工具类,通过ProcessingEnvironment#getElementUtils方法可以获取,其中定义了不少工具方法,如下所示。

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
public interface Elements {

/**
* 通过全限定包名获取唯一的PackageElement
*/
PackageElement getPackageElement(CharSequence name);

/**
* 通过规范名获取唯一的TypeElement
*/
TypeElement getTypeElement(CharSequence name);

/**
* 通过AnnotationMirror获取其中所有Element的值,
*/
Map<? extends ExecutableElement, ? extends AnnotationValue>
getElementValuesWithDefaults(AnnotationMirror a);

/**
* 获取此Element的Java doc信息
*/
String getDocComment(Element e);

boolean isDeprecated(Element e);

Name getBinaryName(TypeElement type);


/**
* 获取此Element的包Element
*/
PackageElement getPackageOf(Element type);

/**
* 获取所有成员Element
*/
List<? extends Element> getAllMembers(TypeElement type);

/**
* 获取所有注解信息
*/
List<? extends AnnotationMirror> getAllAnnotationMirrors(Element e);

boolean hides(Element hider, Element hidden);

boolean overrides(ExecutableElement overrider, ExecutableElement overridden,
TypeElement type);

String getConstantExpression(Object value);

void printElements(java.io.Writer w, Element... elements);

/**
* 将字符序列转换为Name
*/
Name getName(CharSequence cs);

/**
* 判断是否JDK 1.8新增的@FunctionalInterface
*/
boolean isFunctionalInterface(TypeElement type);
}

TypeMirror

TypeMirror是一个接口,表示 Java 编程语言中的类型。类名为什么不直接用Type呢?猜测是为了与反射包中的Type作区分。

TypeMirror表示的类型包括基本类型、声明类型、数组类型、类型变量和null类型,还可以表示通配符类型参数、executable 的签名和返回类型,以及对应于包和关键字 void 的伪类型。

TypeMirror有许多子接口,其类图如下所示。

TypeMirror类图

这里简单介绍一下这些子接口。

类型 说明
ExecutableType 表示一个可执行类型,包括方法、构造器、构造块
IntersectionType 表示一个交集类型,出现在泛型的上下边界中
ReferenceType 表示一个引用类型,包括类和接口类型、数组类型、类型变量和 null 类型
ArrayType 表示一个数组类型
DeclaredType 表示一个声明类型,包括类和接口
TypeVariable 表示一个类型变量,即泛型类型
NullType 表示一个空类型,对应Java中的null
NoType 表示无类型,VOID、PACKAGE和NONE都是NoType
PrimitiveType 表示一个基本类型,包括boolean、byte、short、int、long、char、float和double
WildcardType 表示带通配符的泛型类型

TypeKind

与ElementKind类似,判断TypeMirror类型时我们也应该使用TypeKind来进行。

TypeKind的枚举类型有点多,这里简单介绍部分常量,详细信息请查看官方文档。

类型 说明
BOOLEAN 基本类型 boolean
INT 基本类型 int
LONG 基本类型 long
FLOAT 基本类型 float
DOUBLE 基本类型 double
VOID 对应于关键字 void 的伪类型
NULL null 类型
ARRAY 数组类型
PACKAGE 对应于包元素的伪类型
EXECUTABLE 方法、构造方法或初始化程序

Types

与Elements类似,Types是JDK中提供的辅助操作TypeMiror的工具类,使用ProcessingEnvironment#getTypeUtils方法来获取,方法介绍如下。

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
public interface Types {

/**
* 返回TypeMirror对应的Element
*/
Element asElement(TypeMirror t);

boolean isSameType(TypeMirror t1, TypeMirror t2);

/**
* t1是否是t2的子Type
*/
boolean isSubtype(TypeMirror t1, TypeMirror t2);

/**
* t1是否是t2的父Type
*/
boolean isAssignable(TypeMirror t1, TypeMirror t2);

boolean contains(TypeMirror t1, TypeMirror t2);

/**
* m1的方法签名是否是m2的子签名
*/
boolean isSubsignature(ExecutableType m1, ExecutableType m2);

/**
* 返回t的直接父Type
*/
List<? extends TypeMirror> directSupertypes(TypeMirror t);

/**
* 返回t泛型擦除后的TypeMirror
*/
TypeMirror erasure(TypeMirror t);

/**
* 返回基本类型装箱后的TypeElement
*/
TypeElement boxedClass(PrimitiveType p);

/**
* 返回拆箱后的基本类型
*/
PrimitiveType unboxedType(TypeMirror t);

TypeMirror capture(TypeMirror t);

PrimitiveType getPrimitiveType(TypeKind kind);

NullType getNullType();

NoType getNoType(TypeKind kind);

ArrayType getArrayType(TypeMirror componentType);

/**
* 通过上下边界获取通配符类型
*/
WildcardType getWildcardType(TypeMirror extendsBound,
TypeMirror superBound);

DeclaredType getDeclaredType(TypeElement typeElem, TypeMirror... typeArgs);

DeclaredType getDeclaredType(DeclaredType containing,
TypeElement typeElem, TypeMirror... typeArgs);

TypeMirror asMemberOf(DeclaredType containing, Element element);
}

Filer

Filer类似于Java IO库中的File,用于协助注解处理器创建新文件,这个文件包括源文件、类文件和辅助资源文件。

创建源文件

1
2
3
JavaFileObject createSourceFile(CharSequence name,
Element... originatingElements)
throws IOException

创建一个新的源文件,并返回一个对象以允许写入它。文件的名称和路径(相对于源文件的根目录输出位置)基于该文件中声明的类型。如果声明的类型不止一个,则应该使用主要顶层类型的名称(例如,声明为 public 的那个)。

还可以创建源文件来保存有关某个包的信息,包括包注解。要为指定包创建源文件,可以用 name 作为包名称,后跟 “.package-info”;要为未指定的包创建源文件,可以使用 “package-info”。

创建类文件

1
2
3
JavaFileObject createClassFile(CharSequence name,
Element... originatingElements)
throws IOException

创建一个新的类文件,并返回一个对象以允许写入它。文件的名称和路径(相对于类文件的根目录输出位置)基于将写入的类型名称。还可以创建类文件来保存有关某个包的信息,包括包注解。要为指定包创建类文件,可以用 name 作为包名称,后跟 “.package-info”;为未指定的包创建类文件不受支持。

创建辅助资源文件

1
2
3
4
5
FileObject createResource(JavaFileManager.Location location,
CharSequence pkg,
CharSequence relativeName,
Element... originatingElements)
throws IOException

创建一个用于写入操作的新辅助资源文件,并为它返回一个文件对象。该文件可以与新创建的源文件、新创建的二进制文件或者其他受支持的位置一起被查找。位置 CLASS_OUTPUT 和 SOURCE_OUTPUT 必须受支持。资源可以是相对于某个包(该包是源文件和类文件)指定的,并通过相对路径名从中取出。从不太严格的角度说,新文件的完全路径名将是 location、 pkg 和 relativeName 的串联。

Messager

Messager类似于Android中的Log,用于协助注解处理器报告调试、警告和错误等信息。

一般来说我们在实现注解处理器时需要做好异常处理,因为可能存在不当的使用行为而我们没有考虑到,出现异常时通过Messager来提醒开发者这里有问题。

我们可以通过下列方法来打印信息。

方法 说明
void printMessage(Diagnostic.Kind kind, CharSequence msg) 打印指定种类的消息。
void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e) 在元素的位置上打印指定种类的消息。
void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e, AnnotationMirror a) 在已注解元素的注解镜像位置上打印指定种类的消息。
void printMessage(Diagnostic.Kind kind, CharSequence msg, Element e, AnnotationMirror a, AnnotationValue v) 在已注解元素的注解镜像内部注解值的位置上打印指定种类的消息。

基本实现方法

上面说了这么多,我们还是不知道如何去实现一个注解处理器,这里给出实现的步骤,具体的案例将在后一篇文章《最佳实践|注解处理器实现视图绑定》中详细的介绍。

实现Processor接口

通过实现Processor接口可以自定义注解处理器,这里我们采用更简单的方法通过继承AbstractProcessor类实现自定义注解处理器。实现抽象方法process处理我们想要的功能。

1
2
3
4
5
6
public class BuilderProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
// 处理注解
}
}

除此之外,我们还需要指定支持的注解类型以及支持的Java版本通过重写getSupportedAnnotationTypes方法和getSupportedSourceVersion方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class BuilderProcessor : AbstractProcessor() {

private val supportedAnnotations =
setOf(Builder::class.java, Required::class.java, Optional::class.java)

override fun getSupportedSourceVersion() = SourceVersion.RELEASE_7

override fun getSupportedAnnotationTypes() =
supportedAnnotations.mapTo(HashSet<String>(), Class<*>::getName)

override fun init(p0: ProcessingEnvironment) {
super.init(p0)
// 初始化一些工具类
}

override fun process(annotations: MutableSet<out TypeElement>, env: RoundEnvironment): Boolean {
// 处理注解
}
}

注册注解处理器

最后我们还需要将我们自定义的注解处理器进行注册。新建resources文件夹,目录下新建META-INF文件夹,目录下新建services文件夹,目录下新建javax.annotation.processing.Processor文件,然后将我们自定义注解处理器的全限定名写到此文件,例如我这里:

注册注解处理器

1
com.sukaidev.viewbinding.compiler.BuilderProcessor

到这里我们的注解处理器就能够正常使用了。

具体的案例请参考《最佳实践|注解处理器实现视图绑定》

参考

  1. JSR 269
  2. 《深入理解JVM字节码》
  3. Android注解处理器APT技术探究
  4. 详尽的Android编译时注解处理器教程