JSR-269 Java中的注解处理器:APT工具的原理应用与自定义注解

在这里插入图片描述

🪁🍁 希望本文能给您带来帮助,如果有任何问题,欢迎批评指正!🐅🐾🍁🐥


一、背景

前面有一篇文章在讲述对象赋值转换神器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基于此实现的缓存体系
jsr250java平台Common Annontations 如@PostConstructspring
jsr269Pluggable Annotation Processing APIlombok,mapstruct
jsr303,jsr349,jsr380bean validationhibernate-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);
        }
    }
}

以上为初始化注解处理器的关键代码,大致流程为:

  1. JavaCompiler.initProcessAnnotations()方法
    1. JavacProcessingEnvironmenth构造方法,初始化环境配置
    2. JavacProcessingEnvironment.setProcessors()方法
      1. JavacProcessingEnvironment.initProcessorIterator()方法
        1. JavacProcessingEnvironment#ServiceIterator进行Processor的加载
        2. 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);
	        }
	    }
	}
}

以上为注解处理器逻辑执行的关键代码,大致流程为:

  1. JavaCompiler.processAnnotations()处理注解
  2. JavacProcessingEnvironment.doProcessing()执行处理
    1. JavacProcessingEnvironment#Round多轮调用执行,因为存在编译过程中生成新的文件
      1. JavacProcessingEnvironment#ComputeAnnotationSet查找所有被处理注解打标的文件
      2. JavacProcessingEnvironment#Round.run()执行注解处理器处理
        1. DiscoveredProcessors.discoverAndRunProcs()运行注解处理器
        2. 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 在许多知名的开源框架中都找得到应用,这里列举部分典型框架:

  1. Lombok:Lombok 是一个可以通过注解的方式,让你的 Java 代码更具有简洁性的库。它使用 JSR 269 在编译时生成 getter、setter、equals、hashCode 和 toString 方法,从而减少模板式代码的编写。

  2. Dagger 2:Dagger 2 是一个用于实现依赖注入的框架。它使用 JSR 269 在编译时生成和注入依赖关系的代码,以提高运行时性能。

  3. AutoValue:AutoValue 是 Google 提供的一个生成简洁的、不可修改的自动值类的工具,它使用 JSR 269 在编译时生成这些类的代码。

  4. MapStruct:MapStruct 是一个代码生成库,它基于约定优于配置的方法,利用 JSR 269 在编译时生成对象之间转换的映射代码,从而提高性能。

  5. 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


创作不易,如果有帮助到你的话请给点个赞吧!我是Wasteland,下期文章再见!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wasteland~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值