前言
2021 年了,相信搞 Java 的小伙伴们不会还没有人没用过 Lombok 吧?
Lombok 是一款通过「注解」的形式简化并消除冗余代码的 Java 插件,利用「Annotation Processor」原理,在编译时生成一些「重复」代码。另外需要注意的是,在 IDEA 环境下,需要额外安装一个 Lombok 插件。(本文不会专门介绍 Lombok 的使用方法,想要深入学习的小伙伴可以去 官方文档 学习 Lombok 提供的所有注解的使用方法。)
可能一些朋友对 MapStruct 就有点陌生了,但是我敢肯定的是,你们一定用过和他功能类似的工具。比如 Apache Commons BeanUtils、Spring BeanUtils、BeanCopier、Dozer 等等。没错,MapStruct 也是为了解决对象属性拷贝这一个通用需求的。传统使用「反射」进行属性拷贝的方式,在大数据量的场景下,性能低下,效率堪忧。MapStruct 底层则是通过 getter/setter 的方式提升属性拷贝的性能的,跟 Lombok 一样利用「Annotation Processor」的原理,在编译时生成代码。
踩坑
首先我们按照 MapStruct 官方文档介绍,搭一个简单的栗子🌰~
引入依赖:
<properties>
<mapstruct.version>1.4.2.Final</mapstruct.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
<build>
<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>
<annotationProcessorPaths>
<!-- MapStruct 在编译时会通过这个插件生成代码 -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
创建两个类,用于属性拷贝。
// 源对象
public class CarDO {
private String make;
private int numberOfSeats;
private CarType type;
public CarDO() {
}
public CarDO(String make, int numberOfSeats, CarType type) {
this.make = make;
this.numberOfSeats = numberOfSeats;
this.type = type;
}
public String getMake() {
return make;
}
public void setMake(String make) {
this.make = make;
}
public int getNumberOfSeats() {
return numberOfSeats;
}
public void setNumberOfSeats(int numberOfSeats) {
this.numberOfSeats = numberOfSeats;
}
public CarType getType() {
return type;
}
public void setType(CarType type) {
this.type = type;
}
}
// 测试枚举
public enum CarType {
/**
* 普通
*/
COMMON,
/**
* 老爷车
*/
OLD,
/**
* 跑车
*/
SPORTS;
}
// 目标对象
public class CarDTO {
private String make;
private int seatCount;
private String type;
public CarDTO() {
}
public CarDTO(String make, int seatCount, String type) {
this.make = make;
this.seatCount = seatCount;
this.type = type;
}
public String getMake() {
return make;
}
public void setMake(String make) {
this.make = make;
}
public int getSeatCount() {
return seatCount;
}
public void setSeatCount(int seatCount) {
this.seatCount = seatCount;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
创建一个对象拷贝接口,用于告诉 MapStruct 需要生成哪个对象的属性拷贝。
注意:这里
@Mapper / Mappers / @Mapping都是org.mapstruct包下的,当和 Mybatis 一起使用时,不要引用错了~
@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDTO carToCarDto(CarDO car);
}
最后创建测试类,测试一把!
public class TestMapStruct {
@Test
public void shouldMapCarToDto() {
//given
CarDO car = new CarDO("Morris", 5, CarType.SPORTS);
//when
CarDTO carDto = CarMapper.INSTANCE.carToCarDto(car);
//then
Assert.assertNotNull(carDto);
Assert.assertEquals("Morris", carDto.getMake());
Assert.assertEquals(5, carDto.getSeatCount());
Assert.assertEquals("SPORTS", carDto.getType());
}
}
按照官方文档的做法,十分顺利,测试通过!
如果我们把 2 个实体类里的 getter/setter 替换成 Lombok,会发生什么事情呢?
我们先改造 pom.xml 文件,增加 Lombok 相关依赖:
<properties>
+ <lombok.version>1.18.12</lombok.version>
<mapstruct.version>1.4.2.Final</mapstruct.version>
</properties>
<dependencies>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>${lombok.version}</version>
+ </dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>
此时,我们的实体类代码应该是这样子的:
// 源对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CarDO {
private String make;
private int numberOfSeats;
private CarType type;
}
// 目标对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CarDTO {
private String make;
private int seatCount;
private String type;
}
再来执行一遍测试类,编译时就会出现类似这种「java: 找不到符号 符号: 方法 getXXX()」错误提示,具体如下图:

当出现这种错误,分明就是 Lombok 没有生效嘛,那么该怎么解决这个问题呢?
解坑
还记得文章开头介绍的,Lombok 和 MapStruct 都是利用「Annotation Processor」在程序编译时生成代码的吗?
了解原理,问题就容易解决了。
前文我们在测试 MapStruct 的时候,在 pom.xml 文件中添加了一个一个插件,用于告诉 Maven 编译时,需要额外执行 MapStruct 的代码生成逻辑,但是我们没有告诉 Maven 在编译时,Lombok 也需要生成代码。
我们在 pom.xml 文件的 build 节点中加上这么一段:
<build>
<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>
<annotationProcessorPaths>
<!-- Lombok 在编译时会通过这个插件生成代码 -->
+ <path>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>${lombok.version}</version>
+ </path>
<!-- MapStruct 在编译时会通过这个插件生成代码 -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
这个时候,再去运行一下通过测试类,就不会出现上述找不到 get 方法的错误了~
配套代码:https://github.com/xkcoding/practice_demo/tree/master/lombok-with-mapstruct