Java 注解处理器及其应用

前言

注解作为一种元数据,需要其他地方进行读取,在前面的文章 重识 Java 注解 中我们了解到,在运行时可以通过反射获取注解信息。元注解 @Retention 定义了注解的保留策略,具体有 SOURCE、CLASS、RUNTIME,那么保留策略不为运行时的注解有什么用呢?

除了在运行时获取注解,在编译期其实也可以读取到注解信息。例如保留策略为 SOURCE 的 注解 @Override ,编译器编译源代码时会进行读取,如果发现子类的方法上标注了 @Override ,在父类却不存在对应的方法则会编译失败。

从 JDK 6 开始,Java 提供了注解处理器的一套 API,作为编译器的一个插件,在编译时会执行注解处理器的处理逻辑,以便可以自定义编译时的注解处理逻辑。使用注解处理器的优点便是把运行期处理注解的行为提前到编译器,减少反射的调用,提高程序的性能。

如何使用注解处理器

使用注解处理器有以下步骤。

1、实现 Processor 接口

Processor 是注解处理器的核心接口。实现 Processor 接口的类为自定义的注解处理器,示例如下:

package com.zzuhkp;

import java.util.Set;
import javax.annotation.processing.Completion;
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.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;

/**
 * 自定义注解处理器
 *
 * @author zzuhkp
 * @date 2020-08-14 10:50
 * @since 1.0
 */
public class CustomProcessor implements Processor {
    
    

    /**
     * 获取注解处理器支持的选项,如在命令行指定的选项
     *
     * @return
     */
    @Override
    public Set<String> getSupportedOptions() {
    
    
        return null;
    }

    /**
     * 获取注解处理器支持的注解类型
     *
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
    
    
        return null;
    }

    /**
     * 获取注解处理器支持的源代码版本
     *
     * @return
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
    
    
        return null;
    }

    /**
     * 注解处理器的初始化方法
     *
     * @param processingEnv
     */
    @Override
    public void init(ProcessingEnvironment processingEnv) {
    
    

    }

    /**
     * 处理注解,如果返回true,后面的注解处理器不会继续处理该注解处理器支持的注解
     *
     * @param annotations
     * @param roundEnv
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    
    
        return false;
    }

    /**
     * 这个确实没看懂
     *
     * @param element
     * @param annotation
     * @param member
     * @param userText
     * @return
     */
    @Override
    public Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation,
        ExecutableElement member, String userText) {
    
    
        return null;
    }
}

直接实现 Processor 的较为麻烦,Java 提供了一个抽象类 AbstractProcessor 实现了 Processor,我们只需要继承 AbstractProcessor 即可。

public class CustomProcessor extends AbstractProcessor {
    
    

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    
    
        return false;
    }
}

2、注册注解处理器

自定义注解处理器之后,为了让编译器识别,需要进行注册。具体为在类路径中新建文件/META-INF/services/javax.annotation.processing.Processor,文件内容为自定义的注解处理器全限定名,具体和参见下图。这里使用到了 Java 的 SPI 机制,具体可参见前面的文章 Java 基础知识之 SPI

注册注解处理器

注解处理器生命周期

注解处理器相关的 API 只是定义了一些 接口,由使用注解处理器的工具实现,并按照一定的规范调用注解处理器的方法。注解处理器一轮一轮的进行执行,并且可以生成新的 Java 源代码或 class 文件。生成的文件中又可能包含注解处理器需要处理的注解,因此注解处理器可以处理上一轮生成的注解,直到没有需要处理的注解为止。具体生命周期如下。

  1. 工具首先实例化注解处理器,因此注解处理器需要提供一个无参数的构造方法。
  2. 工具调用 Processor 的 init 方法进行初始化。
  3. 工具调用 Processor 的 getSupportedOptions、getSupportedAnnotationTypes、getSupportedSourceVersion 方法获取注解处理器支持的选项、注解类型、源代码版本号,这些方法只会被调用一次。
  4. 在随后,工具根据需要调用 Processor 的 process 处理注解处理器支持的注解。

注解处理器相关API

Set<String> getSupportedOptions(): 这个方法用来获取注解处理器支持的选项,随后可以通过相关API获取到在工具启动时指定的支持的选项。

Set<String> getSupportedAnnotationTypes():这个方法用来获取注解处理器支持的注解类型,在继承 AbstractProcessor 时还可以通过在类上添加注解 @SupportedAnnotationTypes 指定。

扫描二维码关注公众号,回复: 11896777 查看本文章

SourceVersion getSupportedSourceVersion():这个方法用来获取注解处理器支持的源码版本,在继承 AbstractProcessor 时可以在类上添加注解 @SupportedSourceVersion 指定,如果没有指定则默认为 JDK 6。

void init(ProcessingEnvironment processingEnv):注解处理器实例化后的初始化方法。ProcessingEnvironment 表示注解处理器的环境,可以用来获取一些实用的工具类,包含的方法及含义具体如下。

public interface ProcessingEnvironment {
    
    
    
    /**
     * 从工具启动的选项中获取注解处理器支持的选项
     */
    Map<String,String> getOptions();

    /**
     * 返回用来报告错误,警告或其他信息的工具类。
     * 注意:如果Messager 打印了错误类型的消息,将引发错误。
     */
    Messager getMessager();

    /**
     * 获取创建源文件、class文件的工具类。
     */
    Filer getFiler();

    /**
     * 返回获取元素信息的工具类。
     * 元素是对 Java 程序各部分的抽象,如包、类、变量、方法等。
     */
    Elements getElementUtils();

    /**
     * 返回可以获取类型信息的工具类。
     * 类型是指 Java 中的类型,不完全等同于元素,部分元素和类型可以进行转换。
     */
    Types getTypeUtils();

    /**
     * 获取生成的源文件和class应该支持的版本。
     */
    SourceVersion getSourceVersion();

    /**
     * 获取当前区域信息
     */
    Locale getLocale();
}

boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv):注解处理器的核心方法,处理支持的注解,如果返回true则后面的处理器不会再处理方法参数中的注解。RoundEnvironment 表示当前轮的环境,提供了一些实用的方法,具体如下:

public interface RoundEnvironment {
    
    
    /**
     * 当前是否为最后一轮处理
     */
    boolean processingOver();

    /**
     * 上一轮处理是否引发错误
     */
    boolean errorRaised();

    /**
     * 获取上一轮注解处理生成的根元素
     */
    Set<? extends Element> getRootElements();

    /**
     * 根据注解获取元素
     */
    Set<? extends Element> getElementsAnnotatedWith(TypeElement a);

    /**
     * 根据注解获取元素
     */
    Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a);
}

另外注解处理器还提供了一个方法 Iterable<? extends Completion> getCompletions(Element element, AnnotationMirror annotation, ExecutableElement member, String userText):这个方法确实没看明白,有了解的小伙伴请留言告知,感谢。

Java 注解处理器的应用

笔者了解到 Java 注解处理器有在以下开源项目涉及的场景中进行使用。

属性复制

在项目中,我们常常会将一个类的属性值赋值给另一个对象,常见的做法有三种。

  • 手动创建对象复制,适用于属性较少的情况,如果比较多则会导致代码膨胀。
  • 使用 org.apache.commons.beanutils.BeanUtils#copyProperties,使用反射实现,并添加了属性的校验,性能较差。
  • 使用 org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object),使用反射实现,相对 apache-beanutils 包中的复制属性方法性能较好。

上述是较为常见的做法,还有一种方式是使用开源框架是 mapstruct,它便利用 Java 注解处理器,将运行时通过反射获取属性的行为提前到编译器,生成属性复制的实现方法,提高了性能,具体使用方式可参见 mapstruct 官网

简化代码

项目中,对于实例类,我们经常需要写 get/set/toString/hashCode 等方法。为了减少这些重复的操作,可以使用 lombok 。lombok 同样使用注解处理器,帮助我们生成这些方法。具体的使用可参考 lombok官网

总结

本篇首先引出注解处理器的使用时机,然后介绍了如何使用注解处理器及注解处理器的生命周期,最后介绍了注解处理器的 API 及在开源项目中的使用,相信一定还有其他场景让注解处理器大放异彩。

猜你喜欢

转载自blog.csdn.net/zzuhkp/article/details/107748754