文章目录
- 简介
- 在 Android 中使用 annotationProcessor
- 自定义注解处理器
- 注解处理器中的方法说明
- Elements
- 四种 Element 元素
- 获取要处理的注解 Element
- 校验 Element 元素
- 校验变量类型
- 校验权限修饰符
- 校验 Element 类型
- 获取 Element 元数据
- Messager
- Filer
- SPI(Service Provider Interface) 机制
- 通过 javac 源码分析 APT 执行原理
简介
annotationProcessor 是 javac 的一个工具,全称为 APT(apt 工具,Annotation Processor Tool),它用来在编译时扫描和处理注解,获取注解和被注解对象的相关信息,然后根据注解自动生成 java 代码。
简单理解就是在编译时将 java 文件编译为 class 文件时,annotationProcessor 能通过标记的注解对源码编译过程做一些调整辅助生成一些代码,提高代码执行性能。
在 Android 中使用 annotationProcessor
在编译期间,编译器会定位到 java 源文件中的注解,注解处理器会对其感兴趣的注解进行处理,需要注意的是,一个注解处理器只能产生新的源文件,它不能修改一个已经存在的源文件。
注解处理器一般通过继承 AbstractProcessor 类自定义。但在 Android 开发时可能会发现没有 AbstractProcessor,是因为 Android 平台是基于 OpenJDK,而 OpenJDK 中不包含 annotationProcessor 相关的代码。因此,在使用 annotationProcessor 时,需要在 Android 项目新建一个 module 并选择 Java Library,在该 module 下完成自定义注解处理器。
在 module Java Library 创建自定义注解处理器后,在其他 module 中使用需要在 build.gradle 文件添加 annotationProcessor() 引入依赖:
// 创建的 module 名称为 processor
annotationProcessor project(path: ':processor')
自定义注解处理器
注解处理器中的方法说明
package com.example.processor;
import com.google.auto.service.AutoService;
import java.lang.annotation.Annotation;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
@AutoService(Processor.class) // 注解处理器注册
public class CustomAnnotationProcessor extends AbstractProcessor {
// Element 元素获取工具
private Elements mElementUtils;
// 文件生成工具
private Filer mFiler;
// 日志打印工具
private Messager mMessager;
// 初始化方法
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(processingEnvironment);
mElementUtils = env.getElementUtils();
mFiler = env.getFiler();
mMessager = env.getMessager();
}
// 指定这个注解处理器能够处理的注解类型
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
// 指定注解处理器使用的 Java 版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
// 实现注解处理器的具体业务逻辑
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
return false;
}
}
- init(ProcessingEnvironment env):初始化方法会被注解处理工具调用,并传入 ProcessingEnvironment 类型的参数,这个参数提供很多有用的工具类,例如 Elements、Types、Filer 等
- getSupportedAnnotationTypes():指定这个注解处理器能够处理的注解类型,这个方法返回一个可以支持的注解类型的字符串集合。在 Java7 开始可以在自定义注解处理器类上添加注解 @SupportedAnnotationTypes() 替代该方法
- getSupportedSourceVersion():指定注解处理器使用的 Java 版本,通过返回 SourceVersion.latestSupported() 即可,当然也可以明确指定只支持某个版本的 Java,例如 SourceVersion.RELEASE_7。在 Java7 开始可以在自定义注解处理器类上添加注解 @SupportedSourceVersion() 替代该方法
- process(Set<? extends TypeElement> set, RoundEnvironment env):在这个方法中实现注解处理器的具体业务逻辑,根据输入参数的 RoundEnvironment 可以得到包含特定注解的被注解元素,然后可以编写处理注解的代码最终生成所需的 java 源文件等信息
手动执行注解处理器的注册是繁琐,因此 Google 开源了一个名为 AutoService 的函数库,我们只需要在自定义注解处理器时使用注解 @AutoService(Processor.class) 即可完成注册步骤,帮助我们生成 META-INF/services/javax.annotation.processing.Processor
文件。
使用 @AutoService 注解需要在 module Java Library 引入依赖:
implementation 'com.google.auto.service:auto-service:1.0-rc5'
compileOnly 'com.google.auto.service:auto-service:1.0-rc5'
当然我们也可以自己手动进行注册,在 Android 项目中创建的 module Java Library 目录下,我们创建一个 resource/META-INF/services 目录,该目录下创建 javax.annotation.processing.Processor 文件:
javax.annotation.processing.Processor 文件内容为需要注册的自定义注解处理器全类名,即可完成注册:
最终会通过 SPI 机制反射执行我们自定义的注解处理器,这个后面节点会讲解。
Elements
Elements 是获取 Element 元素的工具,在注解处理器的 init(ProcessingEnvironment env) 参数获取:
private Elements mElementUtils;
@Override
public synchronized void init(ProcessingEnvironment env) {
mElementUtils = env.getElementUtils();
}
四种 Element 元素
Element 是一个接口,可以从 Element 源码可知:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package javax.lang.model.element;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.Set;
import javax.lang.model.AnnotatedConstruct;
import javax.lang.model.type.TypeMirror;
public interface Element extends AnnotatedConstruct {
// 一般再调用 getKind() 用于校验 Element 变量类型
TypeMirror asType();
// 用于校验 Element 类型
ElementKind getKind();
// 用于校验 Element 权限修饰符
Set<Modifier> getModifiers();
// 获取被注解的 Element 名称
// 如果是成员变量,比如 TextView myText,则输出 myText
Name getSimpleName();
Element getEnclosingElement();
List<? extends Element> getEnclosedElements();
boolean equals(Object var1);
int hashCode();
List<? extends AnnotationMirror> getAnnotationMirrors();
// 获取注解处理注解元数据
<A extends Annotation> A getAnnotation(Class<A> var1);
<R, P> R accept(ElementVisitor<R, P> var1, P var2);
}
常用的 Element 元素分为四种:
Element 类型 | 描述 |
PackageElement | 表示包程序元素,提供对有关包及其成员的信息的访问 |
ExecutableElement | 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素 |
TypeElement | 表示一个类或接口程序元素,提供对有关类型及其成员的信息的访问 |
VariableElement | 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数 |
我们用一个例子来说明对应的 Element 元素:
package com.example.annotation; // PackageElement
// TypeElement
public class Test {
// VariableElement
private int i;
// VariableElement
private Triangle triangle;
// ExecutableElement
public Test() {}
// 方法为ExecutableElement,参数为VariableEment
public void method(String s) {}
}
获取要处理的注解 Element
在 process(Set<? extends TypeElement> set, RoundEnvironment env) 方法中处理我们的注解,通过方法 env.getElementsAnnotatedWith() 获取对应注解要处理的 Element 集合:
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
Set<? extends Element> elements = env.getElementsAnnotatedWith(BindView.class);
for (Element element : elements) {
}
return false;
}
校验 Element 元素
虽然在自定义注解时一般会通过 @Target 指定 Annotation 可以注解的对象:
package com.example.processor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD) // 指定注解只能使用在类的成员变量上
public @interface BindView {
int value() default -1;
}
获取对应注解要处理的 Element 集合后,我们还是会进行一次 Element 校验检查是否合法,否则应该抛出异常终止编译。
校验变量类型
TypeKind typeKind = element.asType().getKind();
// 获取类型全类型
// 比如注解 @BindView 是注解在成员变量 TextView
// 则 typeName 输出 android.widget.TextView
String typeName = element.asType().toString();
// 校验是否为 boolean 类型
element.getKind() == TypeKind.BOOLEAN
...
TypeKind 是一个枚举类,可以进去看下还提供了哪些枚举:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package javax.lang.model.type;
public enum TypeKind {
BOOLEAN,
BYTE,
SHORT,
INT,
LONG,
CHAR,
FLOAT,
DOUBLE,
VOID,
NONE,
NULL,
ARRAY,
DECLARED,
ERROR,
TYPEVAR,
WILDCARD,
PACKAGE,
EXECUTABLE,
OTHER,
UNION,
INTERSECTION;
private TypeKind() {
}
public boolean isPrimitive() {
switch(this) {
case BOOLEAN:
case BYTE:
case SHORT:
case INT:
case LONG:
case CHAR:
case FLOAT:
case DOUBLE:
return true;
default:
return false;
}
}
}
校验权限修饰符
Set<Modifier> modifiers = element.getModifiers();
// 校验被注解的 Element 是否包含关键字 public
element.getModifiers().contains(Modifier.PUBLIC)
...
Modifier 是一个枚举类,可以进去查看下还提供了哪些枚举:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package javax.lang.model.element;
import java.util.Locale;
public enum Modifier {
PUBLIC,
PROTECTED,
PRIVATE,
ABSTRACT,
DEFAULT,
STATIC,
FINAL,
TRANSIENT,
VOLATILE,
SYNCHRONIZED,
NATIVE,
STRICTFP;
private Modifier() {
}
public String toString() {
return this.name().toLowerCase(Locale.US);
}
}
校验 Element 类型
ElementKind elementKind = element.getKind();
// 校验被注解的 Element 是否为成员变量
element.getKind() != ElementKind.FIELD
...
ElementKind 是一个枚举类,可以进去查看下还提供了那些枚举:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package javax.lang.model.element;
public enum ElementKind {
PACKAGE, // 包
ENUM, // 枚举
CLASS, // 类
ANNOTATION_TYPE, // 注解
INTERFACE, // 接口
ENUM_CONSTANT, // 枚举常量
FIELD, // 成员变量
PARAMETER, // 参数
LOCAL_VARIABLE, // 本地变量
EXCEPTION_PARAMETER, // 异常参数
METHOD, // 方法
CONSTRUCTOR, // 构造方法
STATIC_INIT,
INSTANCE_INIT,
TYPE_PARAMETER,
OTHER, // 其他
RESOURCE_VARIABLE; // 资源变量
private ElementKind() {
}
public boolean isClass() {
return this == CLASS || this == ENUM;
}
public boolean isInterface() {
return this == INTERFACE || this == ANNOTATION_TYPE;
}
public boolean isField() {
return this == FIELD || this == ENUM_CONSTANT;
}
}
获取 Element 元数据
Element 提供 getAnnotation() 方法获取具体的注解处理注解的元数据。
package com.example.processor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.FIELD)
public @interface BindView {
int value() default -1;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
BindView annotation = element.getAnnotation(BindView.class);
int id = annotation.value();
}
}
Messager
Messager 是可以在编译时打印日志的工具,在注解处理器的 init(ProcessingEnvironment env) 参数获取:
private Messager mMessager;
@Override
public synchronized void init(ProcessingEnvironment env) {
mMessager = env.getMessager();
}
private void error(Element element, String message, Object.. args) {
printMessage(Kind.ERROR, element, message, args);
}
priate void note(Element element, String message, Object... args) {
printMessage(Kind.NOTE, element, message, args);
}
private void printMessage(Kind kind, Element element, String message, Object[] args) {
if (args.length > 0) {
message = String.format(message, args);
}
mMessager.printMessage(kind, message, element);
}
Kind 是接口 Diagnostic 里的一个枚举类,提供了日志打印日志级别:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package javax.tools;
import java.util.Locale;
public interface Diagnostic<S> {
long NOPOS = -1L;
Diagnostic.Kind getKind();
S getSource();
long getPosition();
long getStartPosition();
long getEndPosition();
long getLineNumber();
long getColumnNumber();
String getCode();
String getMessage(Locale var1);
public static enum Kind {
ERROR,
WARNING,
MANDATORY_WARNING,
NOTE,
OTHER;
private Kind() {
}
}
}
Filer
Filer 是编译时编写自动生成文件的工具,在注解处理器的 init(ProcessingEnvironment env) 参数获取:
private Filer mFiler;
@Override
public synchronized void init(ProcessingEnvironment env) {
mFiler = env.getFiler();
}
除了可以自己用字符串手写代码外,编写文件一般使用 Square 提供的 JavaPoet 框架,需要引入依赖:
implementation "com.squareup:javapoet:${latestVersion}"
最终传递 Filer 对象,在编译时自动生成 java 文件。在 Android 项目中,编译后自动生成的文件会存放在 /build/generated/source/apt/debug/com/example/processor/xxx.java
,在新版本的 Android Studio 生成的文件目录是 /build/intermediates/javac/debug/classes/com/example/processor/xxx.java
。
SPI(Service Provider Interface) 机制
SPI(Service Provider Interface)是一种服务发现机制,它通过在与 java 目录同级的 resource/META-INF/services 目录下查找创建的文件,自动加载文件里所定义的类。
在 java 中加载文件使用的是 ServiceLoader。
为了方便演示我们先定义一些测试类:
// 自定义的类
public interface SPIService {
void execute();
}
public class SPIServiceImpl1 implements SPIService {
@Override
public void execute() {
System.out.println("SPIServiceImpl1 execute()")
}
}
public class SPIServiceImpl2 implements SPIService {
@Override
public void execute() {
System.out.println("SPIServiceImpl2 execute()")
}
}
然后我们也在 java 同级目录下新建目录和文件 resources/META-INF/services/com.example.demo.spiservice,在文件定义我们要被 ServiceLoader 加载的类:
// 定义了多少个类就会加载多少个
com.example.demo.spiservice.SPIServiceImpl1
com.example.demo.spiservice.SPIServiceImpl1
最后我们用 ServiceLoader 加载:
// 对象初始化,提供的是接口,拿到的是具体实现类,使用了策略模式
ServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);
Iterator<SPIService> iterator = load.iterator();
// 获取文件的内容,得到实现类的全类名
while (iterator.hasNext()) {
// 反射获取实现类的对象
SPiService spiService = iterator.next();
// 执行实现类中的方法
spiService.execute();
}
这样就能正常的将定义在文件的类执行起来。
ServiceLoader 到底是怎么做到的?我们可以根据上面的步骤先猜测下 SPI 机制的实现步骤:
- 找到 resources/META-INF/services/ 目录,读取该目录里面的文件的字符串
- 为了能拿到类信息,可能会使用反射 Class.forName() 获取
- 拿到类信息后就可以执行
接下来我们跟进源码分析。
public final class ServiceLoader<S>
implements Iterable<S>
{
// 限定了读取的目录
private static final String PREFIX = "META-INF/services/";
private final Class<S> service;
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
public void reload() {
providers.clear();
// 创建 LazyIterator,待循环时获取类对象
lookupIterator = new LazyIterator(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
reload();
}
}
ServiceLoader.load() 就是去初始化创建一个 ServiceLoader,将传进来要实例的 Class 先记录下来。可以发现 ServiceLoader 查找的目录也是固定为 META-INF/services。
ServiceLoader.java
private class LazyIterator
implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
public boolean hasNext() {
return hasNextService();
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 获取读取配置的文件路径 META-INF/services/xxx
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
...
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// IO 流一行行读取文件解析
pending = parse(service, configs.nextElement());
}
// 文件要解析的类名全路径
nextName = pending.next();
return true;
}
public S next() {
return nextService();
}
private S nextService() {
...
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 通过 Class.forName() 反射获取的类信息
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
...
}
...
try {
// 无参构造反射实例化对象然后返回
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
...
}
...
}
}
和我们猜测的一致,ServiceLoader 就是读取的文件解析拿到注册的全限定类名,然后使用 Class.forName() 获取类信息并反射创建的对象。
SPI 机制用这种方式的好处是,框架写好后不再需要修改,而是自定义注解处理器在 resources/META-INF/services 的文件添加注册就能运行起来。
javac 编译过程也是通过 SPI 机制。
通过 javac 源码分析 APT 执行原理
javac 编译也是通过 SPI 机制执行的注解处理器 annotationProcessor。我们通过分析 javac 源码查看 annotationProcessor 是怎么被执行的。
当然,分析源码最好的方式是带着问题分析:
- 注解处理器的 process() 方法是怎么被回调的?
- 注解处理器 process() 的返回值有什么作用?
- 注解处理器的 process() 方法被调用的次数是怎么决定的?
com/sun/tools/javac/Main.java
public class Main {
public static void main(String[] args) throws Exception {
System.exit(compile(args));
}
public static int compile(String[] args) {
com.sun.tools.javac.main.Main compiler =
new com.sun.tools.javac.main.Main("javac");
retur compiler.compile(args).exitCode;
}
}
com/sun/tools/javac/main/Main.java
public Result compile(String[] args) {
Context context = new Context();
JavacFileManager.preRegister(context);
Result result = compile(args, context);
...
return result;
}
public Result compile(String[] args, Context context) {
return compile(args, context, List.<JavaFileObject>nil(), null);
}
public Result compile(String[] args,
Context context,
List<JavaFileObject> fileObjects,
Iterable<? extends Processor> processors)
{
return compile(args, null, context, fileObjects, processors);
}
public Result compile(String[] args,
String[] classNames,
Context context,
List<JavaFileObject> fileObjects,
Iterable<? extends Processor> processors)
{
...
// 处理 java 代码编译的对象
JavaCompiler comp = null;
...
// 创建 java 代码编译对象
comp = JavaCompiler.instance(context);
...
// processors 就是本次编译要执行的注解处理器集合
comp.compile(fileObjects,
classnames.toList(),
processors);
...
}
JavaCompiler.java
public void compile(List<JavaFileObject> sourceFileObjects,
List<String> classnames,
Iterable<? extends Processor> processors)
{
...
// 初始化注解处理器,ServiceLoader 加载
initProcessAnnotations(processors);
// processAnnotations() 执行注解处理器
delegateCompiler =
processAnnotations(
enterTrees(stopIfError(CompileState.PARSE, parseFiles()
classnames);
...
}
javac 是从 com/sun/tools/javac/Main.java 的入口开始执行,通过 JavaCompiler 对象执行的代码编译,执行编译时就有传入 processors 注解处理器集合。处理注解处理器还分为两个步骤:
- initProcessAnnotations():初始化注解处理器
- processAnnotations():执行注解处理器
接下来继续分析处理注解处理器的两个步骤,首先是 initProcessAnnotations():
public void initProcessAnnotations(Iterable<? extends Processor> processors) {
...
procEnvImpl = JavaProcessingEnvironment.instance(context);
procEnvImpl.setProcessors(processors);
...
}
JavaProcessingEnvironment.java
public void setProcessors(Iterable<? extends Processor> processors) {
...
initProcessorIterator(context, processors);
}
private void initProcessorIterator(Context context, Iterable<? extends Processor> processors) {
...
processorIterator = processors.iterator();
...
if (processorNames != null) {
processorIterator = new NameProcessIterator(processorNames, processorClassLoader, log);
} else {
// 执行该句代码,就是在这里使用 ServiceLoader 初始化
processorIterator = new ServiceIterator(processorClassLoader, log);
}
...
discoveredProcs = new DiscoveredProcessors(processorIterator);
...
}
private class ServiceIterator implements Iterator<Processor> {
ServiceIterator(ClassLoader classLoader, Log log) {
// 使用 ServiceLoader 初始化
loader = ServiceLoader.load(Processor.class, classLoader);
this.iterator = loader.iterator();
...
}
}
initProcessAnnotations() 实际上就只做了一件事情:使用 ServiceLoader 初始化注解处理器,SPI 机制就在这里开始的初始化。然后将初始化后的迭代器提供给 DiscoverProcessor 对象。
接下来继续分析第二个步骤,processAnnotations() 执行注解处理器:
JavaCompiler.java
public void compile(List<JavaFileObject> sourceFileObjects,
List<String> classnames,
Iterable<? extends Processor> processors)
{
...
// 初始化注解处理器,ServiceLoader 加载
initProcessAnnotations(processors);
// processAnnotations() 执行注解处理器
delegateCompiler =
processAnnotations(
enterTrees(stopIfError(CompileState.PARSE, parseFiles())
classnames);
...
}
public JavaCompiler processAnnotations(List<JCCompilationUnit> roots,
List<String, classnames) {
...
JavaCompiler c = procEnvImpl.doProcessing(context, roots, classSymbols, pckSymbols,
deferredDiagnosticHandler);
...
}
JavaProcessingEnvironment.java
public JavaCompiler doProcessing(Context context,
List<JCCompilationUnit> roots,
List<ClassSymbol> classSymbols,
Iterable<? extends PackageSymbol> pckSymbols,
Log.DeferredDiagnosticHandler deferredDiagnosticHandler) {
...
// Round 对象是注解处理器的辅助类
Round round = new Round(context, roots, classSymbols, deferredDiagnosticHandler);
...
do {
// 第一次执行 APT
round.run(false, false);
...
} while (...);
}
void run(boolean lastRound, boolean errorStatus) {
...
if (lastRound) {
...
} else {
// 首次是执行该句代码
discoverAndRunProcs(...);
}
...
}
private void discoverAndRunProcs(Context context,
Set<TypeElement> annotationPresent,
List<ClassSymbol> topLevelClasses,
List<PackageSymbol> packageInfoFiles) {
...
for (TypeElement a : annotationPresent) {
unmatchedAnnotations.put(a.getQualifiedName().toString(), a);
}
...
// 在初始化注解处理器时保存了使用 ServiceLoader 初始化的注解处理器集合
DiscoveredProcessors.ProcessorStateIterator psi = discoveredProcs.iterator();
...
// 环境变量
RoundEnvironment renv = new JavacRoundEnvironment(false,
false,
rootElements,
JavaProcessingEnvironment.this);
// 准备循环执行注解处理器
while (unmatchedAnnotations.size() > 0 && psi.hasNext()) {
...
for (Map.Entry<String, TypeElement> entry : unmatchedAnnotations.entrySet()) {
String unmatchedAnnotationName = entry.getKey();
if (ps.annotationSupported(unmatchedAnnotationName)) {
matchedNames.add(unmatchedAnnotationName);
// typeElements 就是传入 process() 的 Set 集合
TypeElement te = entry.getValue();
if (te != null)
typeElements.add(te);
}
}
if (matchedNames.size() > 0 || ps.contributed) {
// 执行注解处理器,并拿到 process() 的返回值
boolean processingResult = callProcessor(ps.processor, typeElements, renv);
...
// 如果 process() 返回 true,就把当前处理的注解在集合删掉
// 这意味着,process() 返回 true 时,下一个注解处理器就拿不到这个注解处理器处理的注解了,不会传递到下一个注解处理器
// 所以一般 process() 没有注解处理时返回 false
if (processingResult) {
unmatchedAnnotations.keySet().removeAll(matchedNames);
}
}
...
}
}
private boolean callProcessor(Processor proc,
Set<? extends TypeElement> tes,
RoundEnvironment renv) {
try {
// 执行注解处理器的 process()
return proc.process(tes, renv);
} catch (...) {
...
}
}
processAnnotations() 执行了注解处理器,通过源码我们也可以解释注解处理器 process() 的返回值的作用:如果 process() 返回 true,就把当前处理的注解在集合删掉,下一个注解处理器就拿不到当前注解处理器处理的注解,不会传递到下一个注解处理器,所以一般没有注解处理时返回 false。
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
return false; // 返回 false 将当前注解处理器处理的注解能传递到下一个注解处理器
}
分析到这里就已经完成了注解处理器的第一次执行。
那么是否会出现多次执行调用的情况呢?
JavaProcessingEnvironment.java
public JavaCompiler doProcessing(Context context,
List<JCCompilationUnit> roots,
List<ClassSymbol> classSymbols,
Iterable<? extends PackageSymbol> pckSymbols,
Log.DeferredDiagnosticHandler deferredDiagnosticHandler) {
...
Round round = new Round(context, roots, classSymbols, deferredDiagnosticHandler);
...
// 循环的意义在于,注解处理器生成的文件有可能会有其他注解没有被处理
// 所以需要再执行一遍
do {
// 执行了 APT,可能有生成新文件
round.run(false, false);
...
// 判断注解处理器是否有生成新文件
moreToDo = moreToDo();
...
// 生成的文件有效,添加到 Round 对象重新进入循环执行
round = round.next(
new LinkedHashSet<JavaFileObject>(filer.getGeneratedSourceFileObjects()),
new LinkedHashMap<String, JavaFileObject>(filer.getGeneratedClasses()));
...
} while (moreToDo && !errorStatus); // 有生成新文件,进入循环重新执行
// 已经执行完,第一个参数 lastRound = true
round.run(true, errorStatus);
...
}
private boolean moreToDo() {
// 判断执行了注解处理器后是否有生成新文件
return filer.newFiles();
}
public boolean newFiles() {
return (!generatedSourceNames.isEmpty())
|| (!generatedClasses.isEmpty());
}
void run(boolean lastRound, boolean errorStatus) {
...
if (lastRound) {
...
// 最后一次执行 APT
discoveredProcs.iterator().runContributingProcs(renv);
} else {
// 非最后一次执行 APT
discoverAndRunProcs(...);
}
...
}
class DiscoveredProcessors implements Iterable<ProcessorState> {
class ProcessorStateIterator implements Iterator<ProcessorState> {
...
public void runContributeingProcs(RoundEnvironment re) {
if (!onProcInterator) {
Set<TypeElement> emptyTypeElements = Collections.emptySet();
while (innerIter.hasNext()) {
ProcessorState ps = innerIter.next();
if (ps.contributed)
// 最后一轮再次执行注解处理器的 process()
// 传入的 set 集合是空的,所以也能解释为什么会有打印为空的情况,就是用来告诉自定义的注解处理器已经执行完成了
// 所以一般我们在 process() 调用时都会添加对 set 是否为空的判断
callProcessor(ps.processor, emptyTypeElements, re);
}
}
}
}
}
在完成第一次的 APT 执行后,会去判断这次执行的注解处理器是否生成了新文件,如果有生成新文件,就会进入循环重新调用一次,直到没有新文件再生成。
也就是说,注解处理器每次生成新文件,APT 就要多执行一次,以保证能处理新建文件中的注解。
当最后一次执行 APT 时,会返回一个空的集合,我们可以通过空集合判断已经是最后一次,通常我们也会在 process() 判断集合是否为空才执行:
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
if (!set.isEmpty()) {
// 集合不为空,执行代码
}
return false;
}
至此,APT 的运行原理解析完毕。
再总结下在分析源码时提出的三个问题:
1、注解处理器的 process() 方法是怎么被回调的?
通过 SPI 机制,ServiceLoader 加载初始化,并通过反射的方式创建实例对象执行。
2、注解处理器 process() 的返回值有什么作用?
如果 process() 返回 true,就把当前处理的注解在集合删掉,下一个注解处理器就拿不到当前注解处理器处理的注解,不会传递到下一个注解处理器;所以一般没有注解处理时返回 false。
3、注解处理器的 process() 方法被调用的次数是怎么决定的?
注解处理器每次生成新文件,APT 就要多执行一次,以保证能处理新建文件中的注解。