
🪁🍁 希望本文能给您带来帮助,如果有任何问题,欢迎批评指正!🐅🐾🍁🐥
一、背景
前面有一篇文章在讲述对象赋值转换神器MapStruct原理时有提到插入式注解处理器,但当时着重讲MapStruct原理,对于插入式注解处理器的相关内容讲的并不是很详细,而插入式注解处理器在我们平时工作中用到的还是比较广的,虽然我们工作中很少涉及这一块的编码,但是用到的很多组件其背后原理都有它的身影,例如checkStyle、FindBug、lombok、MapStruct等插件。因此,本篇文章将详细的讲一下APT的基本概念、实现原理与源码分析以及应用与实战。
二、前置知识
2.1 何谓注解
注解是Java 5开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。
2.2 注解分类
Java中注解分为运行时注解和编译时注解,通过设置元注解@Retention中的RetentionPolicy指定注解的保留策略:
/**
*
* @author Joshua Bloch
* @since 1.5
*/
public enum RetentionPolicy {
/**
* 表示该注解的信息会被编译器抛弃,不会保留在class文件中,注解的信息只会保留在源文件中
*/
SOURCE,
/**
* 表示注解的信息被保留在class文件中,但不会被虚拟机在运行的时候读取
*/
CLASS,
/**
* 表示注解的信息被保留在class文件中,会被虚拟机保留在运行时
*/
RUNTIME
}
编译时注解和运行时注解有如下几大差异:
保留阶段不同:运行时注解保留到运行时,可在运行时访问;而编译时注解保留到编译时,运行时无法访问原理不同:运行时注解是基于Java反射机制,Retrofilt运行时注解;而编译时注解通过注解处理器APT实现性能不同:运行时注解由于使用反射,性能会差一些;而编译时注解性能没影响
2.3 规范提案
JSR是Java Specification Requests的缩写,意思是Java 规范提案,是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
| jsr | 描述 | 相关实现 |
|---|---|---|
| jsr107 | 缓存规范 | spring基于此实现的缓存体系 |
| jsr250 | java平台Common Annontations 如@PostConstruct | spring |
| jsr269 | Pluggable Annotation Processing API | lombok,mapstruct |
| jsr303,jsr349,jsr380 | bean validation | hibernate-validitor |
2.4 何谓注解处理器
APT(Annotation Processing Tool)是 Java 中的一种编译时工具,用于在编译阶段处理注解(Annotation)。APT 的主要作用是通过解析和处理源代码中的注解,生成额外的源代码、配置文件或其他资源。注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描和处理注解(Annotation)的工具。APT 在Java 5中引入,但在Java 6之后被Pluggable Annotation Processing API取代,后者提供了更强大和灵活的注解处理机制。下文中对其就不作区分,把它们理解为一个东西,都统称为注解处理器或APT。
三、注解处理器的实现原理与源码分析
3.1 注解处理器组成部分
正如前置知识中提到的JSR269定义了Pluggable Annotation Processing API,它允许开发者在编译时处理注解。JSR269主要包括两个部分:javax.annotation.processing包和javax.lang.model包,具体说明如下:
-
Pluggable Annotation Processing API核心接口和类
-
javax.annotation.processing.Processor:注解处理器的接口
-
javax.annotation.processing.AbstractProcessor:注解处理器的抽象基类
-
javax.annotation.processing.RoundEnvironment:提供当前处理轮次的环境信息
-
javax.lang.model.element.Element:表示被注解的元素(如类、方法、字段等)
-
3.2 注解处理器工作时机
javac(java compiler)是java语言编译器,其负责读取由java语言编写的类和接口的定义,并将它们编译成字节码文件。javac编译器的工作流程如下图所示:

从javac代码的总体结构来看 ,编译过程大致纷纷那位1个准备过程和3个处理过程,它们分别为:
- 准备过程:初始化插入时注解处理器
- 解析与填充符号表的过程
词法、语法分析:将源代码的字符流转变为标记集合,构造出抽象语法树填充符号表:产生符号地址和符号信息
- 插入时注解处理器的注解处理过程
- 分析与字节码生成过程
标注检查:对语法的静态信息进行检查数据流及控制流分析:对程序动态运行过程进行检查解语法糖:将简化代码编写的语法糖还原为原有的形式字节码生成:将前面各个步骤所生成的信息转化成字节码
3.3 注解处理器组成源码分析
3.3.1 Element
我们要想弄清楚APT的实现原理,首先我们需要对Element接口及子接口乃至其对应的方法有一些基本的认知。🧐
public interface Element extends javax.lang.model.AnnotatedConstruct {
/**
* @return the type defined by this element
*/
TypeMirror asType();
/**
* 返回element的类型,判断是哪种element
*/
ElementKind getKind();
/**
* 获取修饰关键字,如public static final等关键字
*/
Set<Modifier> getModifiers();
/**
* 获取名字,不带包名
*/
Name getSimpleName();
/**
* 返回该元素之间包含的子元素
*/
List<? extends Element> getEnclosedElements();
@Override
boolean equals(Object obj);
@Override
int hashCode();
@Override
List<? extends AnnotationMirror> getAnnotationMirrors();
@Override
<A extends Annotation> A getAnnotation(Class<A> annotationType);
<R, P> R accept(ElementVisitor<R, P> v, P p);
}
总结如下表:
| 类名称 | 说明 |
|---|---|
| PackageElement | 表示一个包程序元素,提高对有关包及其成员信息的访问 |
| ExecutableElement | 表示某个类或接口的方法、构造方法或初始化程序 |
| TypeElement | 表示某个类或接口元素,提供对有关类型及其成员的信息访问 |
| VariableElement | 表示一个字段、enum常量、方法或构造方法参数、局部变量或异常参数 |
| 方法名称 | 说明 |
|---|---|
| getEnclosedElements() | 返回该元素之间包含的子元素 |
| getEnclosingElement() | 返回包含该元素的父元素,与上一个方法相反 |
| getKind() | 返回element的类型,判断是哪种element |
| getModifiers() | 获取修饰关键字,如public static final等关键字 |
| getSimpleName() | 获取名字,不带包名 |
| getQualifiedName() | 获取全名,如果是类的话,包含完整的包名路径 |
| getParameters() | 获取方法的参数元素,每个元素是一个VariableElement |
| geReturnType() | 获取方法元素的返回值 |
| getConstantValue() | 如果属性变量呗final修饰,则可以使用该方法获取它的值 |
3.3.2 AbstractProcessor
如果我们要自定义注解处理器,那么必须继承AbstractProcessor类,因此,理解这个类的方法至关重要。AbstractProcessor源码如下所示:
public abstract class AbstractProcessor implements Processor {
// 注解处理工具的集合,包含了众多工具类:如Filer用来编写新文件,Messager用来打印错误,Elements用来处理Element
protected ProcessingEnvironment processingEnv;
private boolean initialized = false;
/**
* Constructor for subclasses to call.
*/
protected AbstractProcessor() {}
/**
*
* 配置支持的选项,如果不重写这个方法,AbstractProcessor类中默认的实现会读取@SupportedOption注解的配置
*/
public Set<String> getSupportedOptions() {
SupportedOptions so = this.getClass().getAnnotation(SupportedOptions.class);
if (so == null)
return Collections.emptySet();
else
return arrayToSet(so.value());
}
/**
*
* 配置支持的注解类型,如果不重写这个方法,AbstractProcessor类中默认的实现会读取@SupportedAnnotationTypes注解的配置
*/
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());
}
/**
*
* 配置支持的JDK版本,如果不重写这个方法,AbstractProcessor类中默认的实现会读取@SupportedSourceVersion注解的配置
*/
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);
/**
* @param userText {@inheritDoc}
*/
public Iterable<? extends Completion> getCompletions(Element element,
AnnotationMirror annotation,
ExecutableElement member,
String userText) {
return Collections.emptyList();
}
/**
* @return {@code true} if this object has been initialized,
* {@code false} otherwise.
*/
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);
}
}
对于ProcessingEnvironment接口对应会有实现类JavacProcessingEnvironment类,JavacProcessingEnvironment负责管理注解处理器的生命周期、提供编译上下文信息,并协调注解处理与编译过程。JavacProcessingEnvironment其中使用到的有几个工具类比较重要,对其作用总结如下表:
| 类名称 | 说明 |
|---|---|
| JavacFiler | 用来创建新的Java源文件、Class文件及辅助文件 |
| Messager | 用来报告错误、警告或其他提示信息 |
| JavacElements | 实现了javax.lang.model.util.Elements接口,用于操作Element的工具方法 |
| JavacTypes | 实现了javax.lang.model.util.Types接口,用于操作TypeMirror的工具方法 |
3.4 注解处理器工作源码分析
根据前文注解处理器工作时机章节不难发现,注解处理器工作主要分为两个部分:初始化注解处理器阶段和注解处理器逻辑执行阶段。那么本小节也是按这两部分分别去介绍,这两部分的实现背后都依托JavaCompiler类。
由于注解处理器是在编译阶段被调用,而在Java中使用javac命令去编译源文件时,实际上是去执行com.sun.tools.javac.Main#main方法,而真正执行编译动作的是 Java 编译器的核心类com.sun.tools.javac.main.JavaCompiler类,它是 JDK 中 javac 命令行工具的实现,属于 JDK 内部实现的一部分,但并不是公开的 API。com.sun.tools.javac.main.JavaCompiler 类位于 tools.jar 文件中,tools.jar 文件通常位于 JDK 安装目录的 lib 文件夹中。在 JDK 8 及以前的版本中,tools.jar 是单独的文件;在 JDK 9 及以后版本,JDK 已经迁移到模块化系统,javac 的相关工具被拆分到不同的模块中,而不再通过 tools.jar 提供。
注意:由于注解处理器工作在编译期,涉及到很多编译源码,因此,要想理清楚这个调用过程最好是本地IDEA调试,本地编译调试的方法在前文《【MapStruct】深入浅出带你学会从编译调试走进MapStruct源码(二)》已经有介绍,这里不做赘述。
3.4.1 初始化注解处理器
JavaCompiler在词法分析(抽象语法树)时会初始化注解处理器,初始化注解处理器的方法调用源码如下,调用链路按照介绍顺序从前往后。
JavaCompiler
// com/sun/tools/javac/main/JavaCompiler下方法
public void compile(List<JavaFileObject> var1, List<String> var2, Iterable<? extends Processor> var3) {
...
// 初始化注解处理器
this.initProcessAnnotations(var3);
...
}
public void initProcessAnnotations(Iterable<? extends Processor> var1) {
...
// JavacProcessingEnvironment的实例化
this.procEnvImpl = JavacProcessingEnvironment.instance(this.context);
// 设置注解处理器
this.procEnvImpl.setProcessors(var1);
this.processAnnotations = this.procEnvImpl.atLeastOneProcessor();
...
}
JavacProcessingEnvironment
// com/sun/tools/javac/processing/JavacProcessingEnvironment下方法
protected JavacProcessingEnvironment(Context var1) {
...
this.filer = new JavacFiler(var1);
this.messager = new JavacMessager(var1, this);
this.elementUtils = JavacElements.instance(var1);
this.typeUtils = JavacTypes.instance(var1);
this.processorOptions = this.initProcessorOptions(var1);
this.unmatchedProcessorOptions = this.initUnmatchedProcessorOptions();
this.messages = JavacMessages.instance(var1);
this.taskListener = MultiTaskListener.instance(var1);
this.initProcessorClassLoader();
}
private void initProcessorIterator(Context var1, Iterable<? extends Processor> var2) {
...
// 首先获取-processor命令指定的注解处理器,
String var8 = this.options.get(Option.PROCESSOR);
if (this.processorClassLoaderException == null) {
// 当配置了-processpath命令时,会在此路径下调用fileManager.getClassLoader()方法创建对应的类加载器,
// 否则在classpath路径下创建对应的类加载器。
// 如果获取到的值processorNames不为空,也就是配置了-processor命令
if (var8 != null) {
// 那么创建一个NameProcessIterator迭代器对象
var4 = new NameProcessIterator(var8, this.processorClassLoader, var3);
} else {
// 否则创建一个ServiceIterator迭代器对象
var4 = new ServiceIterator(this.processorClassLoader, var3);
}
} else {
...
}
...
// 初始化discoveredProcs变量
this.discoveredProcs = new DiscoveredProcessors((Iterator)var4);
}
🌟JavacProcessingEnvironment#ServiceIterator:SPI机制的典型体现
private class ServiceIterator implements Iterator<Processor> {
// 迭代器包裹的Processor
private Iterator<Processor> iterator;
// 记录日志
private Log log;
// Processor的加载器
private ServiceLoader<Processor> loader;
ServiceIterator(ClassLoader var2, Log var3) {
this.log = var3;
try {
try {
// SPI机制的体现
// 利用ServiceLoader找到META-INF/services下Processor接口文件注册的自定义processor
this.loader = ServiceLoader.load(Processor.class, var2);
this.iterator = this.loader.iterator();
} catch (Exception var5) {
this.iterator = JavacProcessingEnvironment.this.handleServiceLoaderUnavailability("proc.no.service", (Exception)null);
}
} catch (Throwable var6) {
var3.error("proc.service.problem", new Object[0]);
throw new Abort(var6);
}
}
...
}
JavacProcessingEnvironment#DiscoveredProcessors
// src/com/sun/tools/javac/processing/JavacProcessingEnvironment#DiscoveredProcessors
// DiscoveredProcessors类实现了Iterable<ProcessorState>接口,
// 使用迭代器类ProcessorStateIterator来迭代ProcessorState对象,
// 这个迭代器也是借助processorIterator来完成注解处理器迭代的
class DiscoveredProcessors implements Iterable<ProcessorState> {
// processorIterator保存了NameProcessIterator或ServiceIterator对象
Iterator<? extends Processor> processorIterator;
// procStateList保存了当前已经被封装为ProcessorState对象的所有注解处理器
ArrayList<ProcessorState> procStateList;
...
}
JavacProcessingEnvironment#ProcessorState
// src/com/sun/tools/javac/processing/JavacProcessingEnvironment#ProcessorState
static class ProcessorState {
// 保存的具体的注解处理器
public Processor processor;
// contributed表示此注解处理器是否运行过process()方法,如果运行过process()方法,则这个变量的值将被设置为true
public boolean contributed;
// 保存了注解处理器能够处理的注解类型
private ArrayList<Pattern> supportedAnnotationPatterns;
// 保存了注解处理器能够处理的注解选项
private ArrayList<String> supportedOptionNames;
ProcessorState(Processor var1, Log var2, Source var3, ProcessingEnvironment var4) {
this.processor = var1;
this.contributed = false;
try {
// 注解处理器的初始化
this.processor.init(var4);
// 校验注解处理器支持的Java源代码版本
this.checkSourceVersionCompatibility(var3, var2);
this.supportedAnnotationPatterns = new ArrayList();
Iterator var5 = this.processor.getSupportedAnnotationTypes().iterator();
String var6;
while(var5.hasNext()) {
var6 = (String)var5.next();
// 调用注解处理器的getSupportedAnnotationType()方法来获取支持处理的注解类型
// 并添加到supportedAnnotationPatterns集合中
this.supportedAnnotationPatterns.add(JavacProcessingEnvironment.importStringToPattern(var6, this.processor, var2));
}
this.supportedOptionNames = new ArrayList();
var5 = this.processor.getSupportedOptions().iterator();
while(var5.hasNext()) {
var6 = (String)var5.next();
if (this.checkOptionName(var6, var2)) {
// 调用注解处理器的getSupportedOptions()方法来获取支持的注解选项
this.supportedOptionNames.add(var6);
}
}
} catch (ClientCodeException var7) {
throw var7;
} catch (Throwable var8) {
throw new AnnotationProcessingError(var8);
}
}
}
以上为初始化注解处理器的关键代码,大致流程为:
JavaCompiler.initProcessAnnotations()方法JavacProcessingEnvironmenth构造方法,初始化环境配置JavacProcessingEnvironment.setProcessors()方法JavacProcessingEnvironment.initProcessorIterator()方法JavacProcessingEnvironment#ServiceIterator进行Processor的加载JavacProcessingEnvironment#DiscoveredProcessors对加载的Processor进行包装
3.4.2 注解处理器逻辑执行
JavaCompiler
// com/sun/tools/javac/main/JavaCompiler下方法
public void compile(List<JavaFileObject> var1, List<String> var2, Iterable<? extends Processor> var3) {
...
// 调用parseFiles()方法得到List<JCCompilationUnit>对象
// 调用enterTrees()方法完成符号的输入
// 这个方法会完成符号输入的第一与第二阶段,
// 同时也会对声明及定义的语法树节点进行标注,
// 因此才能在后续的注解处理器运行阶段操作TypeMirror与Element
this.delegateCompiler = this.processAnnotations(this.enterTrees(this.stopIfError(CompileState.PARSE, this.parseFiles(var1))), var2);
...
}
public JavaCompiler processAnnotations(List<JCTree.JCCompilationUnit> var1, List<String> var2) {
...
// 在这之前会判断是否有必要调用JavacProcessingEnvironment类的doProcessing()方法运行注解处理器
// 调用方法后将返回一个新的JavaCompiler对象,
// 使用这个对象将继续执行Java源代码的编译,
// 所以说注解处理器能够影响Javac的编译过程
JavaCompiler var17 = this.procEnvImpl.doProcessing(this.context, var1, var3, var4, this.deferredDiagnosticHandler);
...
}
JavacProcessingEnvironment.doProcessing
// src/com/sun/tools/javac/processing/JavacProcessingEnvironment
public JavaCompiler doProcessing(Context var1, List<JCTree.JCCompilationUnit> var2, List<Symbol.ClassSymbol> var3, Iterable<? extends Symbol.PackageSymbol> var4, Log.DeferredDiagnosticHandler var5) {
...
/*
如果注解处理器运行process()方法后产生了新的Java源文件,
Javac会重新运行一轮注解处理器,因此只要运行一轮注解处理器后有新的Java源文件产生后,
就会接着重新运行一轮注解处理器,直到没有新的文件产生
*/
do {
// 运行第一轮的注解处理器
// 调用Round对象的run()方法来执行注解处理的逻辑,
// Round对象代表了循环调用注解处理器处理语法树的过程
var12.run(false, false);
// 当运行完这一轮注解处理器时,如果没有发现错误并且又有新的文件
// 生成时,需要进行下一轮注解处理器
var13 = var12.unrecoverableError();
// 如果有新的文件产生,也就是当调用moreToDo()方法返回true时
// 需要调用当前Round对象的next()方法得到一个新的Round对象,
// 并将保存了新产生的文件的集合传递给新的Round对象
var9 = this.moreToDo();
var12.showDiagnostics(var13 || this.showResolveErrors);
// 调用round.next()方法创建新的Round对象
// 每一次循环都会创建一个Round对象
var12 = var12.next(new LinkedHashSet(this.filer.getGeneratedSourceFileObjects()), new LinkedHashMap(this.filer.getGeneratedClasses()));
if (var12.unrecoverableError()) {
var13 = true;
}
} while(var9 && !var13);
// 运行最后一轮注解处理器
var12.run(true, var13);
var12.showDiagnostics(true);
this.filer.warnIfUnclosedFiles();
this.warnIfUnmatchedOptions();
if (this.messager.errorRaised() || this.werror && var12.warningCount() > 0 && var12.errorCount() > 0) {
var13 = true;
}
LinkedHashSet var10 = new LinkedHashSet(this.filer.getGeneratedSourceFileObjects());
var2 = cleanTrees(var12.roots);
JavaCompiler var11 = var12.finalCompiler();
...
}
JavacProcessingEnvironment#Round
// com/sun/tools/javac/processing/JavacProcessingEnvironment/Round
// 创建一个新的Round
Round(Context var2, List<JCTree.JCCompilationUnit> var3, List<Symbol.ClassSymbol> var4, Log.DeferredDiagnosticHandler var5) {
...
// 调用getTopLevelClasses()方法就是将roots列表中保存的所有编译单元下定义的顶层类追加到topLevelClasses列表中
this.topLevelClasses = JavacProcessingEnvironment.this.getTopLevelClasses(var3).prependList(var4.reverse());
this.packageInfoFiles = JavacProcessingEnvironment.this.getPackageInfoFiles(var3);
// 调用findAnnotationsPresent()方法查找在topLevelClasses列表的顶层类中使用到的注解类型
this.findAnnotationsPresent();
}
// 调用findAnnotationsPresent()方法查找在topLevelClasses列表的顶层类中使用到的注解类型
// 查找所有使用到的注解类型并保存到Round类的annotationsPresent中
void findAnnotationsPresent() {
// 通过ComputeAnnotationSet类对语法树进行扫描,找到使用到的注解类型
ComputeAnnotationSet var1 = new ComputeAnnotationSet(JavacProcessingEnvironment.this.elementUtils);
this.annotationsPresent = new LinkedHashSet();
Iterator var2 = this.topLevelClasses.iterator();
while(var2.hasNext()) {
Symbol.ClassSymbol var3 = (Symbol.ClassSymbol)var2.next();
var1.scan(var3, (Set)this.annotationsPresent);
}
var2 = this.packageInfoFiles.iterator();
while(var2.hasNext()) {
Symbol.PackageSymbol var4 = (Symbol.PackageSymbol)var2.next();
var1.scan(var4, (Set)this.annotationsPresent);
}
}
// 运行注解处理器
void run(boolean var1, boolean var2) {
...
try {
// 第一轮注解处理器的调用时,lastRound为false
// lastRound为true,标识最后一轮注解处理器
if (var1) {
JavacProcessingEnvironment.this.filer.setLastRound(true);
Set var3 = Collections.emptySet();
JavacRoundEnvironment var4 = new JavacRoundEnvironment(true, var2, var3, JavacProcessingEnvironment.this);
JavacProcessingEnvironment.this.discoveredProcs.iterator().runContributingProcs(var4);
} else {
// 第一轮时调用
JavacProcessingEnvironment.this.discoverAndRunProcs(this.context, this.annotationsPresent, this.topLevelClasses, this.packageInfoFiles);
}
} catch (Throwable var8) {
this.deferredDiagnosticHandler.reportDeferredDiagnostics();
this.log.popDiagnosticHandler(this.deferredDiagnosticHandler);
throw var8;
} finally {
if (!JavacProcessingEnvironment.this.taskListener.isEmpty()) {
JavacProcessingEnvironment.this.taskListener.finished(new TaskEvent(Kind.ANNOTATION_PROCESSING_ROUND));
}
}
...
}
JavacProcessingEnvironment#ComputeAnnotationSet
public static class ComputeAnnotationSet extends ElementScanner8<Set<TypeElement>, Set<TypeElement>> {
final Elements elements;
public ComputeAnnotationSet(Elements var1) {
this.elements = var1;
}
public Set<TypeElement> visitPackage(PackageElement var1, Set<TypeElement> var2) {
return var2;
}
// 在这个方法中查找所有已被使用的注解类型
public Set<TypeElement> scan(Element var1, Set<TypeElement> var2) {
this.addAnnotations(var1, var2);
return (Set)super.scan(var1, var2);
}
void addAnnotations(Element var1, Set<TypeElement> var2) {
Iterator var3 = this.elements.getAllAnnotationMirrors(var1).iterator();
// 如果找到,就将注解类型对应的Element对象存到p集合中,
// 也就是保存到了Round类中定义的类型为Set<TypeElement>的annotationsPresent集合。
while(var3.hasNext()) {
AnnotationMirror var4 = (AnnotationMirror)var3.next();
Element var5 = var4.getAnnotationType().asElement();
var2.add((TypeElement)var5);
}
}
}
JavacProcessingEnvironment#DiscoveredProcessors
// src/com/sun/tools/javac/processing/JavacProcessingEnvironment#DiscoveredProcessors
// 运行注解处理器
private void discoverAndRunProcs(Context var1, Set<TypeElement> var2, List<Symbol.ClassSymbol> var3, List<Symbol.PackageSymbol> var4) {
// 需要处理的元素的全限定名和元素的映射
HashMap var5 = new HashMap(var2.size());
Iterator var6 = var2.iterator();
// 建立全限定名到对应TypeElement对象的映射关系
while(var6.hasNext()) {
TypeElement var7 = (TypeElement)var6.next();
var5.put(var7.getQualifiedName().toString(), var7);
}
// 让处理"*"的注解处理器也有机会运行
if (var5.size() == 0) {
var5.put("", (Object)null);
}
// 可通过迭代器获取所有的注解处理器
DiscoveredProcessors.ProcessorStateIterator var16 = this.discoveredProcs.iterator();
LinkedHashSet var17 = new LinkedHashSet();
var17.addAll(var3);
var17.addAll(var4);
Set var18 = Collections.unmodifiableSet(var17);
// 准备这一轮Round运行的环境
// renv就是在调用注解处理器的process()方法时传递的第2个RoundEnvironment类型的参数
// renv会保存上一轮Round运行后的一些状态,可以在覆写process()方法时调用相关方法获取这些信息进行逻辑处理
JavacRoundEnvironment var8 = new JavacRoundEnvironment(false, false, var18, this);
// 当有待处理的注解并且有注解处理器的情况下,查找能处理注解的注解处理器并运行
while(var5.size() > 0 && var16.hasNext()) {
// 调用psi.next()方法获取ProcessorState对象
ProcessorState var9 = var16.next();
// 匹配出可以被处理的注解名
HashSet var10 = new HashSet();
// 匹配出可以被处理的元素
LinkedHashSet var11 = new LinkedHashSet();
Iterator var12 = var5.entrySet().iterator();
// 查找注解处理器能够处理的注解并存储到matchedNames集合中
// 当var12集合中存在注解类型并且也能查找到注解处理器时,查找能处理这些注解类型的注解处理器并运行
while(var12.hasNext()) {
Map.Entry var13 = (Map.Entry)var12.next();
String var14 = (String)var13.getKey();
// 从var12集合中查找是否含有能被当前的注解处理器ps处理的注解类型
if (var9.annotationSupported(var14)) {
var10.add(var14);
TypeElement var15 = (TypeElement)var13.getValue();
if (var15 != null) {
// 添加匹配出可以被处理的元素
// TypeElement对象就是注解处理器覆写process()方法时接收的
// 第一个Set<? extends TypeElement>类型的参数,表示此注解处理器处理的注解类型
var11.add(var15);
}
}
}
// 当注解处理器ps能够处理某些注解或者在之前的Round中运行过此注解处理器时
if (var10.size() > 0 || var9.contributed) {
// 调用callProcessor()方法运行此注解处理器
boolean var19 = this.callProcessor(var9.processor, var11, var8);
var9.contributed = true;
var9.removeSupportedOptions(this.unmatchedProcessorOptions);
if (this.printProcessorInfo || this.verbose) {
this.log.printLines("x.print.processor.info", new Object[]{var9.processor.getClass().getName(), var10.toString(), var19});
}
// 注解处理器执行成功,移除处理器名称
if (var19) {
var5.keySet().removeAll(var10);
}
}
}
...
// 再次运行之前Round中运行过的注解处理器
var16.runContributingProcs(var8);
...
}
// 运行在procStateList中剩下的还没有运行过的注解处理器
public void runContributingProcs(RoundEnvironment var1) {
if (!this.onProcInterator) {
Set var2 = Collections.emptySet();
while(this.innerIter.hasNext()) {
ProcessorState var3 = (ProcessorState)this.innerIter.next();
if (var3.contributed) {
JavacProcessingEnvironment.this.callProcessor(var3.processor, var2, var1);
}
}
}
}
以上为注解处理器逻辑执行的关键代码,大致流程为:
JavaCompiler.processAnnotations()处理注解JavacProcessingEnvironment.doProcessing()执行处理JavacProcessingEnvironment#Round多轮调用执行,因为存在编译过程中生成新的文件JavacProcessingEnvironment#ComputeAnnotationSet查找所有被处理注解打标的文件JavacProcessingEnvironment#Round.run()执行注解处理器处理DiscoveredProcessors.discoverAndRunProcs()运行注解处理器DiscoveredProcessors.runContributingProcs()运行在procStateList中剩下的还没有运行过的注解处理器
四、自定义注解处理器的实现步骤
我们想实现自己的注解处理器项目需要遵循下述步骤即可:
1. 自定义注解。
2. 继承AbstractProcessor类,并且重写process方法,在process方法中实现自己的注解处理逻辑。
3. 在META-INF/services目录下创建javax.annotation.processing.Processor文件注册自己实现的Annotation Processor。
4. 编译项目并deploy jar包到maven仓库,在其他项目引入该jar包,在接口上加上我们自定义的注解。
这里还想强调一点的是注解处理器实现依赖SPI机制,根据上面介绍的初始化注解处理器部分源码就不难发现。SPI机制在前面的文章《SPI机制:Java SPI原理及源码剖析、应用场景分析与自实现案例实战详解》中已经有介绍过,感兴趣的朋友可以前往阅读,这里不做赘述。
五、注解处理器的广泛应用
注解处理器的应用是非常广泛的,无论是Android开发中,如EventBus、ARouter、ButterKnife等流行框架都使用了该技术,还是我们普通java开发中经常使用的工具Lombok、MapStruct等。
JSR 269 在许多知名的开源框架中都找得到应用,这里列举部分典型框架:
-
Lombok:Lombok 是一个可以通过注解的方式,让你的 Java 代码更具有简洁性的库。它使用 JSR 269 在编译时生成 getter、setter、equals、hashCode 和 toString 方法,从而减少模板式代码的编写。
-
Dagger 2:Dagger 2 是一个用于实现依赖注入的框架。它使用 JSR 269 在编译时生成和注入依赖关系的代码,以提高运行时性能。
-
AutoValue:AutoValue 是 Google 提供的一个生成简洁的、不可修改的自动值类的工具,它使用 JSR 269 在编译时生成这些类的代码。
-
MapStruct:MapStruct 是一个代码生成库,它基于约定优于配置的方法,利用 JSR 269 在编译时生成对象之间转换的映射代码,从而提高性能。
-
Immutables:Immutables 是一个生成不可变对象和 builders 的库,它使用 JSR 269 在编译时生成这些代码。
这些都是 JSR 269 的典型应用,它们通过在编译时生成代码,以提高代码执行的效率和减少运行时的负载。
六、注解处理器自实现练手案例
在学习了注解处理器的源码和开源工具中对注解处理器的应用后,本章节来自己实现一个自定义注解和注解处理器,本节中以实现一个@ToString注解为例,Lombok中原有@ToString注解带来的控制台打印效果如下:

这里我们想实现一个自己的@ToString注解,而且想让这个中括号换成大括号,那么需要怎么做呢?流程是先需要创建一个customprocessor项目,用于定义自定义注解和注解处理器,然后将其打包成jar包并发到本地仓库中,然后在另一个项目中引入验证即可。customprocessor项目总体结构如下图红框标出所示,然后其实现步骤按照本文第四章节介绍的实现即可。

6.1 自定义注解
首先,我们需要自定义一个注解,如本文中定义ToString,由于是编译时注解,因此记得选择好保留策略。源码定义如下:
/**
* @author wasteland
* @create 2025-03-04
*/
@Target(ElementType.TYPE) // 该注解只能用于类上
@Retention(RetentionPolicy.SOURCE) // 注解仅在源码阶段保留
public @interface ToString {
}
6.2 继承AbstractProcessor类
然后我们需要自定义一个Processor类继承AbstractProcessor类,主要需要重写它的processor方法。根据上面介绍的注解处理器逻辑执行时机也能看出,它的触发执行逻辑主要会去操作JCTree,因此实现自定义处理器时会比较频繁使用它的相关方法,本文对JCTree语法树相关知识只做基本总结,更多相关知识有兴趣的朋友可以在网上查阅。
com.sun.tools.javac.tree.JCTree 是语法树元素的基类,包含以下重要的子类:
- JCStatement:声明语法树节点
- JCBlock:语句块语法树节点
- JCReturn:return语句语法树节点
- JCClassDecl:类定义语法树节点
- JCVariableDecl:字段/变量定义语法树节点
- JCMethodDecl:方法定义语法树节点
- JCModifiers:访问标志语法树节点
- JCExpression:表达式语法树节点
- JCAssign:赋值语句语法树节点
- JCIdent:标识符语法树节点,可以是变量,类型,关键字等
JCTree 利用的是访问者模式,将数据与数据的处理进行解耦,而利用访问者 TreeTranslator,可以访问 JCTree 上的类定义节点 JCClassDecl,进而可以获取类中的成员变量、方法等节点并进行修改。因此基于JCTree 相关知识,ToStringProcessor的具体源码如下:
/**
* @author wasteland
* @create 2025-03-04
*/
@SupportedAnnotationTypes("com.wasteland.customprocessor.annotation.ToString") // 指定支持的注解类型
@SupportedSourceVersion(javax.lang.model.SourceVersion.RELEASE_8) // 指定支持的java版本
public class ToStringProcessor extends AbstractProcessor {
protected Messager messager; // 用来在编译期打log用的
protected JavacTrees trees; // 提供了待处理的抽象语法树
protected TreeMaker treeMaker; // 封装了创建AST节点的一些方法
protected Names names; // 提供了创建标识符的方法
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
processingEnv = jbUnwrap(ProcessingEnvironment.class, processingEnv);
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.trees = JavacTrees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
this.treeMaker = TreeMaker.instance(context);
this.names = Names.instance(context);
}
/**
* 获取 IDEA 环境下的 ProcessingEnvironment
*/
private static <T> T jbUnwrap(Class<? extends T> iface, T wrapper) {
T unwrapped = null;
try {
final Class<?> apiWrappers = wrapper.getClass().getClassLoader().loadClass("org.jetbrains.jps.javac.APIWrappers");
final Method unwrapMethod = apiWrappers.getDeclaredMethod("unwrap", Class.class, Object.class);
unwrapped = iface.cast(unwrapMethod.invoke(null, iface, wrapper));
}
catch (Throwable ignored) {}
return unwrapped != null? unwrapped : wrapper;
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 获取被 @ToString 注解修饰的元素
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(ToString.class);
set.forEach(element -> {
// 根据元素获取对应的语法树 JCTree
JCTree jcTree = trees.getTree(element);
jcTree.accept(new TreeTranslator() {
// 处理语法树的类定义部分 JCClassDecl
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
// 检查是否已经存在 toString 方法
if (!hasToStringMethod(jcClassDecl)) {
// 创建 toString 方法
createToStringMethod(jcClassDecl);
}
super.visitClassDef(jcClassDecl);
}
});
});
return true;
}
private boolean hasToStringMethod(JCTree.JCClassDecl jcClassDecl) {
for (JCTree tree : jcClassDecl.defs) {
if (tree.getKind().equals(Tree.Kind.METHOD)) {
JCTree.JCMethodDecl methodDecl = (JCTree.JCMethodDecl) tree;
if (methodDecl.getName().contentEquals("toString")) {
return true;
}
}
}
return false;
}
private void createToStringMethod(JCTree.JCClassDecl classDecl) {
// 构建toString()的方法体
JCTree.JCBlock methodBody = buildToStringMethodBody(classDecl);
// 创建toString()方法
JCTree.JCMethodDecl toStringMethod = treeMaker.MethodDef(
treeMaker.Modifiers(1), // public
names.fromString("toString"), // 方法名
treeMaker.Ident(names.fromString("String")), // 返回类型
List.nil(), // 泛型参数
List.nil(), // 参数列表
List.nil(), // 异常列表
methodBody, // 方法体
null // 默认值(用于注解方法)
);
// 将toString()方法添加到类中
classDecl.defs = classDecl.defs.append(toStringMethod);
}
private JCTree.JCBlock buildToStringMethodBody(JCTree.JCClassDecl classDecl) {
// 构建方法体的语句
List<JCTree.JCStatement> statements = List.nil();
// 开始构建返回值
JCTree.JCExpression returnValue = treeMaker.Literal(classDecl.name + "{");
// 遍历类的字段
boolean firstField = true;
for (JCTree def : classDecl.defs) {
if (def instanceof JCTree.JCVariableDecl) {
JCTree.JCVariableDecl field = (JCTree.JCVariableDecl) def;
// 添加字段名和值
if (!firstField) {
returnValue = treeMaker.Binary(
JCTree.Tag.PLUS,
returnValue,
treeMaker.Literal(", ")
);
}
returnValue = treeMaker.Binary(
JCTree.Tag.PLUS,
returnValue,
treeMaker.Binary(
JCTree.Tag.PLUS,
treeMaker.Literal(field.name + "="),
treeMaker.Ident(field.name)
)
);
firstField = false;
}
}
// 添加 "}"
returnValue = treeMaker.Binary(
JCTree.Tag.PLUS,
returnValue,
treeMaker.Literal("}")
);
// 创建 return 语句
JCTree.JCReturn returnStatement = treeMaker.Return(returnValue);
statements = statements.append(returnStatement);
// 创建方法体
return treeMaker.Block(0, statements);
}
}
6.3 注册自定义处理器
在 META-INF/services 目录下创建一个以javax.annotation.processing.Processor命名的文件,文件内容为自定义注解处理器的的全限定名,如本文中的com.wasteland.customprocessor.processor.ToStringProcessor。实现过程如下图:

6.4 编译打包
我们首先需要在项目根目录下执行mvn clean install命令,打包完成后会在本地仓库生成相应的jar包文件,当打包完成后可以自行追踪到本地仓库确认,由于我这里本地仓库是 E:\openrepository,因此打包完效果如下图:

正常的情况下会非常顺利,但如果在打包过程中遇到如下问题😤 ,也不用着急,都会有解决方法的😀。
问题1:控制台显示服务配置不正确

解决方式: 去掉javax.annotation.processing.Processor文件,等编译完成后,再将文件拷贝到对应的目录去(但每次编译后都需要拷贝),然后再运行install命令即可。
问题2:控制台显示source.tree不存在

解决方式: 在pom文件里引入依赖
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
6.5 引入与验证
要使用自己实现的工具,第一步肯定少不了引入依赖,上一步deploy完成后的GAV坐标如下:
<dependency>
<groupId>com.wasteland</groupId>
<artifactId>customProcessor</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
引入依赖后,在待测试的实体类上加上@ToString注解,注意下导包的路径即可,打印效果如下图:

到这里实现自定义的@ToString注解就已经完成了,有兴趣的朋友可以自己实现@Getter和@Setter注解,或者开发出更多的功能。
七、总结
在日常开发中,我们可能很少直接写注解处理器相关的代码,但是在工作过程中肯定是不少使用过,像耳熟能详的lombok、mapstruct等工具,它们背后原理正如我本文中介绍的,就是充分使用了注解处理器。因此搞清楚注解处理器的原理是非常必要的,它能给我们提供启发,给我们提供了一种优化思路,将运行时注解处理前置到编译期,这样能提高程序运行速度。
八、参考资料
https://blog.csdn.net/weixin_37598243/article/details/128356697
https://segmentfault.com/a/1190000043647678#item-0-3


3950

被折叠的 条评论
为什么被折叠?



