当Lombok遇见了MapStructの「坑」

前言

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 方法的错误了~


创建时间:2021-04-22 14:01:21

原文链接:xkcoding.com/2021/04/22/…

配套代码:github.com/xkcoding/pr…