前言
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 官方文档介绍,搭一个简单的栗子🌰~
引入依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <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> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
|
创建两个类,用于属性拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| 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 一起使用时,不要引用错了~
1 2 3 4 5 6 7
| @Mapper public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
@Mapping(source = "numberOfSeats", target = "seatCount") CarDTO carToCarDto(CarDO car); }
|
最后创建测试类,测试一把!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class TestMapStruct { @Test public void shouldMapCarToDto() { CarDO car = new CarDO("Morris", 5, CarType.SPORTS);
CarDTO carDto = CarMapper.INSTANCE.carToCarDto(car);
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 相关依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <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>
|
此时,我们的实体类代码应该是这样子的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @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
节点中加上这么一段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <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