JDK APT(Annotation Processing Tool) 编译时注解处理器

博文目录


javac

The javac Command - processor-path

先看下面几个 javac 命令参数的说明

  • -proc:[none, only, full]
    • Controls whether annotation processing and compilation are done. -proc:none means that compilation takes place without annotation processing. -proc:only means that only annotation processing is done, without any subsequent compilation. If this option is not used, or -proc:full is specified, annotation processing and compilation are done.
    • 控制是否完成注解处理和编译。-proc:none 表示在没有注解处理的情况下进行编译。-proc:only 意味着只进行注解处理,而不进行任何后续编译。如果未使用此选项,或指定 -proc:full 则完成注解处理和编译。
  • -processor class1[,class2,class3...]
    • Names of the annotation processors to run. This bypasses the default discovery process.
    • 要运行的注解处理器的名称。这绕过了默认的发现进程。
  • --processor-module-path path
    • Specifies the module path used for finding annotation processors.
    • 指定用于查找注解处理器的模块路径。
  • --processor-path path or -processorpath path
    • Specifies where to find annotation processors. If this option is not used, then the class path is searched for processors.
    • 指定在何处找到注解处理器。如果未使用此选项,则在类路径中搜索处理器。

大致意思是, javac 命令有 注解处理编译 的功能, 常规流程是先处理注解, 待完成后再执行编译操作

默认情况下, javac 会从类路径中搜索注解处理器(如何搜索下面说), 然后执行其注解处理逻辑, 最后执行编译流程

这里说的注解处理功能就是 APT, 全称 Annotation Processing Tool. 具体的注解处理逻辑由注解处理器定义, 通常都是生成一些新的源码, 源码参与后续编译, 从而增强对应类与功能, 常见的有 Lombok, MapStruct, MyBatis-Flex 等

Annotation Processing

The javac Command - Annotation Processing

javac 编译命令提供了注解处理的直接支持, 相关 API 被定义在 javax.annotation.processing 和 javax.lang.model 包和子包中

How Annotation Processing Works

除非使用 -proc:none 选项禁用注解处理,否则编译器将搜索任何可用的注解处理器。可以使用 -processorpath 选项指定搜索路径。如果未指定路径,则使用用户类路径。处理器通过搜索路径上名为 META-INF/services/javax.annotation.Processing.Processor 的服务提供者配置文件定位(就是判断各个 jar 包中是否有这个文件)。这些文件应该包含要使用的任何注解处理器的名称,每行列出一个。或者,也可以使用 -processor 选项显式指定处理器(就是说 -processorpath 和 -processor 两个参数二选一)。

在扫描命令行上的源文件和类以确定存在哪些注解之后,编译器将查询处理器以确定它们处理哪些注解。找到匹配的注解时调用处理器。处理器可以声明它所处理的注解,在这种情况下,不会进一步尝试为这些注解寻找任何处理器(就是说一旦找到某注解的一个处理器就不再继续寻找)。在所有注解都被声明处理之后,编译器不会搜索其他处理器(就是说如果有多余的处理器没有匹配到注解, 这些处理器也不会被调用了)。

如果任何处理器生成新的源文件,那么就会进行另一轮注解处理: 扫描所有新生成的源文件,并像以前一样处理注解。在前几轮调用的任何处理器也在后面的所有轮调用。这种情况一直持续到不生成新的源文件为止。

在没有生成新源文件的情况下进行一轮后,将最后一次调用注解处理器,以便它们有机会完成剩余的任何工作。最后,除非使用 -proc:only 选项,否则编译器将编译原始文件和所有生成的源文件。

如果使用注解处理器生成要包含在编译中的其他源文件,则可以指定用于新生成的文件的默认模块,以便在不生成模块声明时使用。在这种情况下,使用 --default-module-for-create-files 选项。

Compilation Environment and Runtime Environment

Javac 分析源文件和之前编译的类文件中的声明时,使用的编译环境不同于用来执行 javac 本身的运行时环境。尽管很多 javac 选项和 Java 启动程序的类似命名选项之间存在一定的相似性,比如 --class-path--module-path 等等,但是重要的是要理解,一般来说 javac 选项只影响源文件编译的环境,并不影响 javac 本身的操作。

在使用注解处理器时,编译环境和运行时环境之间的区别是显著的。虽然注解处理器处理编译环境中存在的元素(声明) ,但是注解处理器本身是在运行时环境中执行的。如果注解处理器依赖于不在模块中的库,那么可以将这些库以及注解处理器本身放在处理器路径上。(–processor-path 选项)。如果注解处理器及其依赖项位于模块中,则应改为使用处理器模块路径。(–processor-module-path 选项)。当这些不足够时,可能需要提供进一步的运行时环境配置。

  • 如果从命令行调用 javac,则可以通过在选项前面加上 -J 将选项传递给底层运行时。
  • 您可以直接启动 Java 虚拟机的一个实例,并使用命令行选项和 API 来配置一个环境,在这个环境中可以通过其中一个 API 调用 javac。

maven-compile-plugin

Maven 编译插件提供了 APT 支持

  • annotationProcessorPaths
    • 指定注解处理器的多个搜索路径。如果指定了,编译器将只在这些类路径元素中检测注解处理器。如果省略,则使用默认类路径来检测注解处理器。检测本身依赖于 annotationProcessors 配置。
    • 每个类路径元素都使用它们的 Maven 坐标 (groupId、 artifactId、 version)指定。
  • annotationProcessorPathsUseDepMgmt
    • 在解析注解处理器路径的传递依赖关系时,是否使用 Maven 依赖关系管理部分。
    • 此标志不启用/禁用从依赖项管理部分解析注解处理程序路径版本的能力。它只影响那些顶级路径的传递依赖关系的解析。
  • annotationProcessors
    • 要运行的注解处理程序的名称。只适用于 JDK 1.6 +,如果没有设置,则应用默认的注解处理程序发现过程。

对 Maven pom 中配置注解处理器的理解

javac 执行过程中, 先扫描源文件以确定存在哪些注解, 之后编译器在类路径中逐个检索处理器以确定能处理哪些注解, 然后逐个调用找到的处理器, 循环这些过程直到不再生成新的源文件. 最后才是执行编译

  • 不指定编译时的注解相关参数, 编译器会在所有类路径中, 逐个找到注解处理器并执行
  • 指定注解处理器的检索路径时, 编译器只会在指定的类路径中, 逐个找到注解处理器并执行

换句话说, 只要把注解处理器配置到 dependencies 标签中即可, 并不需要特地去配置 maven-compile-plugin 的 annotationProcessorPaths, 但是一旦配置了 annotationProcessorPaths, 就需要把所有涉及到的注解处理器都配置过来, 因为编译时只会在配置的类路径中查找注解处理器

类路径, 这里有一个 Maven 编译时的例子, classpath 中, 放在首位的是自己写的类, 然后是根据 pom dependencies 配置的 mapstruct, mapstruct-processor, lombok 这三个依赖包, 编译器会按照这个顺序查找注解处理器
在这里插入图片描述

Lombok, MapStruct, MyBatis-Flex 说明

MyBatis-Flex 和 Lombok、Mapstruct 整合
MapStruct - Can I use MapStruct together with Project Lombok?

Lombok 中, 开发使用的 @Slf4j, @Getter, @Setter 等注解以及处理这些注解的处理器, 都直接放在了同一个 lombok 包中. MapStruct 中, 开发使用的 @Mapper, @Mappers 等注解以及以来的 Mappers 类都放在了 mapstruct 包中, 而处理这些注解的处理器放在了 mapstruct-processor 包中. MyBatis-Flex 中, 开发使用的各种依赖都放在了 mybatis-fles-core 包中, 而自动生成 TableDef 类的处理器放在了 mybatis-flex-processor 包中

MapStruct 依赖 Lombok 生成的 Getter 和 Setter 等方法, MyBatis-Flex 不确定是否依赖 Lombok, 盲猜不依赖

MyBatis-Flex 官网推荐把 lombok, lombok-mapstruct-bidding, mapstruct-processor, mybatis-flex-processor 按先后顺序配置到 Maven Compile 插件的 annotationProcessorPaths 中

而 MapStruct 官网给出的示例代码中, 同样配置到了 Maven Compile 插件的 annotationProcessorPaths 中, 但顺序是 mapstruct-processor, lombok, lombok-mapstruct-binding

根据我们对 APT 的理解, 处理器可以在 dependencies 中配置, 配置到 Maven Compile 插件中也行, 当然, 配置到插件中是有好处的, 检索处理器的速度更快, 也能避免多模块项目或者其他依赖传递造成类路径先后顺序的改变

通过做几个小实验, 来理解编译器是如何 逐个 在类路径或指定的处理器检索路径检索处理器的

测试

新建一个测试的 Maven 项目, 用于测试 Lombok 和 MapStruct 是否受 pom.xml 配置的影响

提供 User 和 UserDto 两个类, 提供 UserMapper 用于 User 转 UserDto

通过修改 lombok 和 mapstruct-processor 的先后位置, 判断先后顺序是否影响注解处理器工作

结果是, 会影响

  • 当只配置 dependencies 没配置 maven-compile-plugin 的 annotationProcessorPaths
    • 当 lombok 在 mapstruct-processor 前时, 可正常编译通过
@Getter
@Setter
@ToString
public class User {
    private String username;
}

@Getter
@Setter
@ToString
public class UserDto {
    private String username;
}

@Mapper
public interface UserMapper {
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    @Mapping(source = "username", target = "username")
    UserDto userToUserDto(User user);
}

只在 dependencies 中配置 Lombok 和 MapStruct

当 lombok 放在 mapstruct-processor 前面时, 可以正常处理注解并编译通过

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.32</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.5.5.Final</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>1.5.5.Final</version>
    </dependency>
</dependencies>

当 lombok 放在 mapstruct-processor 后面时, 报错 No property named "username" exists in source parameter(s). Type "User" has no properties.

<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.5.5.Final</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>1.5.5.Final</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.32</version>
    </dependency>
</dependencies>

当 lombok 放在 mapstruct-processor 后面, 但提供了 lombok-mapstruct-binding 时, 可以正常处理注解并编译通过

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.5.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.5.Final</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.32</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok-mapstruct-binding</artifactId>
    <version>0.2.0</version>
</dependency>

当 lombok, mapstruct-processor, lombok-mapstruct-binding 任意排序时, 可以正常处理注解并编译通过

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok-mapstruct-binding</artifactId>
    <version>0.2.0</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.5.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.5.Final</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.32</version>
</dependency>

由此可猜测编译过程中, 类路径参数的组织是按照 pom dependencies 从上到下的顺序来的, 且 lombok-mapstruct-binding 的作用是取消 MapStruct 和 Lombok 强制顺序依赖, 让 lombok, mapstruct-processor, lombok-mapstruct-binding 可以配置任意顺序

在 compile 插件中配置 Lombok 和 MapStruct

当 lombok 放在 mapstruct-processor 前面时, 可以正常处理注解并编译通过

<path>
  <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.32</version>
</path>
<path>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.5.Final</version>
</path>

当 lombok 放在 mapstruct-processor 后面时, 报错 No property named "username" exists in source parameter(s). Type "User" has no properties.

<path>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.5.Final</version>
</path>
<path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.32</version>
</path>

当 lombok 放在 mapstruct-processor 后面, 但提供了 lombok-mapstruct-binding 时, 可以正常处理注解并编译通过

<path>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.5.Final</version>
</path>
<path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.32</version>
</path>
<path>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok-mapstruct-binding</artifactId>
    <version>0.2.0</version>
</path>

测试结果与只在 dependencies 中配置完全一致

结论

APT, 编译时注解处理器, 在编译器编译源码前, 编译器会按照类路径的顺序扫描注解处理器, 注解处理器主要负责生成各种附加源码, 处理器流程执行结束后, 编译器会将手写源码与生成源码(生成在 target/generated-sources 目录下)一同编译

默认情况下, 编译器会从用户类路径下逐个查找注解处理器, 但如果指定了注解处理器查找路径参数, 则只从指定的路径中逐个查找

Maven pom dependencies 配置依赖的顺序就是最终用户类路径的顺序

maven-compile-plugin 中 annotationProcessorPaths 配置注解处理器的顺序就是最终查找注解处理器的顺序, 优先级高于 dependencies, 有这个配置就不会按照 dependencies 配置的类路径查找注解处理器了, 且这种方式配置的注解处理器的查找速度应该会快一点

处理器 mapstruct-processor 需要依赖 处理器 lombok 的执行结果, 依赖其生成的 Getter 和 Setter 方法, Maven pom dependencies 中 lombok 要放在 mapstruct-processor 前面. 因为 Maven 项目可能有多层嵌套模块, 也可能存在复杂的依赖传递, 在各种因素的影响下, 最终 Maven 执行计划中, dependencies 里 lombok, mapstruct-processor 的顺序可能与预期不一致, 导致编译失败

Lombok 提供了另一个处理器 lombok-mapstruct-binding, 其作用是取消 MapStruct 和 Lombok 强制顺序依赖, 让 lombok, mapstruct-processor, lombok-mapstruct-binding 可以配置任意顺序

又因为, 将注解处理器配置到编译插件中速度会快一点, 所以后续 Lombok 和 MapStruct 建议如下配置方式, 先后顺序随意即可

<dependencies>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.32</version>
    </dependency>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>1.5.5.Final</version>
    </dependency>
</dependencies>
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.12.0</version>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>1.18.32</version>
                    </path>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok-mapstruct-binding</artifactId>
                        <version>0.2.0</version>
                    </path>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>1.5.5.Final</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值