博文目录
文章目录
- javac
- Annotation Processing
- How Annotation Processing Works
- Compilation Environment and Runtime Environment
- maven-compile-plugin
- 对 Maven pom 中配置注解处理器的理解
- Lombok, MapStruct, MyBatis-Flex 说明
- Maven 配置顺序
- 编译正常
- 编译错误
javac_4">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?
<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 前时, 可正常编译通过
java">@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.