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

发布于:2024-05-10 ⋅ 阅读:(19) ⋅ 点赞:(0)

博文目录


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, 就需要把所有涉及到的注解处理器都配置过来, 因为编译时只会在配置的类路径中查找注解处理器

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 包中

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <annotationProcessorPaths>
        	<!-- Lombok 相关注解处理器 -->
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${org.projectlombok.version}</version>
            </path>
            <!-- Lombok 版本大于等于 1.18.16 时使用 MapStruct 需加入下述依赖 -->
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok-mapstruct-binding</artifactId>
                <version>${lombok-mapstruct-binding.version}</version>
            </path>
            <!-- MapStruct 相关注解处理器 -->
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>${org.mapstruct.version}</version>
            </path>
            <!-- MyBatis-Flex 相关注解处理器 -->
            <path>
                <groupId>com.mybatis-flex</groupId>
                <artifactId>mybatis-flex-processor</artifactId>
                <version>${mybatis-flex.version}</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

MyBatis-Flex 推荐上述配置方式, 用于与 Lombok, MapStruct 一起使用, Lombok 放最前, 其次是 MapStruct, 然后是 MyBatis-Flex

MapStruct 中关于如何与 Lombok 一起使用, 做出了说明, MapStruct 需要用到 Getter Setter 方法, 所以要在 Lombok 之后工作

Lombok 是一个注解处理器,它(除了其他功能之外)将 getter 和 setter 添加到编译 bean 类的 AST (抽象语法树)中。对 AST 的修改是 Java 注解处理 API 无法预见的,所以 Lombok 和 MapStruct 需要一些技巧来使它们一起工作。基本上,MapStruct 要等到 Lombok 完成所有修改之后,才能为增强了 Lombok 的 bean 生成 mapper 类。

如果您正在使用 Lombok 1.18.16 或更新版本,您还需要添加 lombok-mapstruct-binding 注解处理器以使 Lombok 和 MapStruct 协同工作。

MapStruct 给出的示例代码中, annotationProcessorPaths 中配置的 Lombok, MapStruct 并不需要考虑先后顺序

<?xml version="1.0" encoding="UTF-8"?>
<!--

 Copyright MapStruct Authors.

 Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0

-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.mapstruct.examples.lombok</groupId>
    <artifactId>mapstruct-examples-lombok</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <org.mapstruct.version>1.6.0.Beta1</org.mapstruct.version>
        <org.projectlombok.version>1.18.30</org.projectlombok.version>
        <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>

        <!-- lombok dependencies should not end up on classpath -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${org.projectlombok.version}</version>
            <scope>provided</scope>
        </dependency>

        <!-- IntelliJ pre 2018.1.1 requires the mapstruct processor to be present as provided dependency -->
<!--        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version>
            <scope>provided</scope>
        </dependency>-->

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
            <version>4.13.1</version>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <!-- See https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html -->
                        <!-- Classpath elements to supply as annotation processor path. If specified, the compiler   -->
                        <!-- will detect annotation processors only in those classpath elements. If omitted, the     -->
                        <!-- default classpath is used to detect annotation processors. The detection itself depends -->
                        <!-- on the configuration of annotationProcessors.                                           -->
                        <!--                                                                                         -->
                        <!-- According to this documentation, the provided dependency processor is not considered!   -->
                        <annotationProcessorPaths>
                            <path>
                                <groupId>org.mapstruct</groupId>
                                <artifactId>mapstruct-processor</artifactId>
                                <version>${org.mapstruct.version}</version>
                            </path>
                            <path>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                                <version>${org.projectlombok.version}</version>
                            </path>
                            <path>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok-mapstruct-binding</artifactId>
                                <version>${lombok-mapstruct-binding.version}</version>
                            </path>
                        </annotationProcessorPaths>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

Maven 配置顺序

新建一个测试的 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>
    <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>

编译错误

<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>
No property named "username" exists in source parameter(s). Type "User" has no properties.

网站公告

今日签到

点亮在社区的每一天
去签到