Young Kbt blog Young Kbt blog
首页
  • java基础

    • Java基础
    • Java集合
    • Java反射
    • JavaJUC
    • JavaJVM
  • Java容器

    • JavaWeb
  • Java版本新特性

    • Java新特性
  • SQL 数据库

    • MySQL
    • Oracle
  • NoSQL 数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • ActiveMQ
    • RabbitMQ
    • RocketMQ
    • Kafka
  • 进阶服务

    • Nginx
  • Spring
  • Spring Boot
  • Spring Security
  • 设计模式
  • 算法
  • 知识
  • 管理

    • Maven
    • Git
  • 部署

    • Linux
    • Docker
    • Jenkins
    • Kubernetes
  • 进阶

    • TypeScript
  • 框架

    • React
    • Vue2
    • Vue3
  • 轮子工具
  • 项目工程
  • 友情链接
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 关于
    • Vue2-Admin (opens new window)
    • Vue3-Admin(完善) (opens new window)
GitHub (opens new window)

Shp Liu

朝圣的使徒,正在走向编程的至高殿堂!
首页
  • java基础

    • Java基础
    • Java集合
    • Java反射
    • JavaJUC
    • JavaJVM
  • Java容器

    • JavaWeb
  • Java版本新特性

    • Java新特性
  • SQL 数据库

    • MySQL
    • Oracle
  • NoSQL 数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • ActiveMQ
    • RabbitMQ
    • RocketMQ
    • Kafka
  • 进阶服务

    • Nginx
  • Spring
  • Spring Boot
  • Spring Security
  • 设计模式
  • 算法
  • 知识
  • 管理

    • Maven
    • Git
  • 部署

    • Linux
    • Docker
    • Jenkins
    • Kubernetes
  • 进阶

    • TypeScript
  • 框架

    • React
    • Vue2
    • Vue3
  • 轮子工具
  • 项目工程
  • 友情链接
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 关于
    • Vue2-Admin (opens new window)
    • Vue3-Admin(完善) (opens new window)
GitHub (opens new window)
  • 设计模式

  • 算法

  • 知识

    • 知识 - 对象
    • 知识 - 幂等性
    • 知识 - 分布式事务
    • 知识 - MapStruct
    • 知识 - MapStructPlus
      • 引入依赖
      • 简单使用
      • 使用指南
        • 简单转换
        • 对象的属性自动转换
        • 自定义属性转换
        • 不同属性名称映射
        • 指定时间格式转换
        • 指定数字格式转换
        • 忽略指定属性的转换
        • 属性转换时的默认值
        • 表达式
        • 自定义类型转换器
        • 自动接入自定义转换接口
        • 反向属性映射配置
    • 知识 - CompletableFuture
    • 知识 - EasyExcel
    • 知识 - Encrypt
    • 知识 - 接口幂等性
    • 知识 - 数据脱敏
    • 知识 - WebSocket
    • 知识 - Spring Cache
    • 知识 - 请求日志输出
    • 知识 - 接口限流
  • 开发
  • 知识
Young Kbt
2024-01-06
目录

知识 - MapStructPlus

  • 引入依赖
  • 简单使用
  • 使用指南
    • 简单转换
    • 对象的属性自动转换
    • 自定义属性转换
    • 自定义类型转换器
    • 自动接入自定义转换接口
    • 反向属性映射配置

上一个 MapStruct 文档 介绍了基本的 JavaBean 转换工具,但是需要额外新建 Convertor 类将两个 JavaBean 转换,下面介绍 MapStructPlus 工具,

这是社区大佬基于 MapStruct 开发的,内嵌 Mapstruct,使用 注解 的时候就可以将 JavaBean 直接进行转换,和 Mapstruct 完全兼容,如果之前已经使用 Mapstruct,可以无缝替换依赖。

# 引入依赖

<properties>
  	<java.version>JDK 版本,如 17</java.version>
    <mapstruct-plus.version>最新版本,如 1.3.5</mapstruct-plus.version>
  	<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
</properties>

<dependencies>
    <!-- mapstructPlus 依赖 -->
    <dependency>
        <groupId>io.github.linpeilie</groupId>
        <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
        <version>${mapstruct-plus.version}</version>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>${maven-compiler-plugin.version}</version>
            <configuration>
                <source>${java.version}</source>
        				<target>${java.version}</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>io.github.linpeilie</groupId>
                        <artifactId>mapstruct-plus-processor</artifactId>
                        <version>${mapstruct-plus.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

如果使用了 Lombok,两者是有冲突的,那么解决的方案如下:

lombok 1.18.16 之前:

<properties>
  	<java.version>JDK 版本,如 17</java.version>
    <mapstruct-plus.version>最新版本,如 1.3.5</mapstruct-plus.version>
  	<lombok.version>最新版本,如 1.18.30</lombok.version>
  	<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
		<!-- 插件版本 -->
  	<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
</properties>

<dependencies>
    <!-- mapstructPlus 依赖 -->
    <dependency>
        <groupId>io.github.linpeilie</groupId>
        <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
        <version>${mapstruct-plus.version}</version>
    </dependency>
  	<!--lombok-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>${lombok.version}</version>
      <optional>true</optional>
    </dependency>
</dependencies>

<build>
  <plugins>
    <!-- 解决 MapStruct 和 Lombok 冲突 -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>${maven-compiler-plugin.version}</version>
      <configuration>
        <source>${java.version}</source>
        <target>${java.version}</target>
        <annotationProcessorPaths>
          <path>
            <groupId>io.github.linpeilie</groupId>
            <artifactId>mapstruct-plus-processor</artifactId>
            <version>${mapstruct-plus.version}</version>
          </path>
          <path>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.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

lombok 1.18.16 及之后:

<properties>
  	<java.version>JDK 版本,如 17</java.version>
    <mapstruct-plus.version>最新版本,如 1.3.5</mapstruct-plus.version>
  	<lombok.version>最新版本,如 1.18.30</lombok.version>
  	<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
		<!-- 插件版本 -->
  	<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
</properties>

<dependencies>
    <!-- mapstructPlus 依赖 -->
    <dependency>
        <groupId>io.github.linpeilie</groupId>
        <artifactId>mapstruct-plus-spring-boot-starter</artifactId>
        <version>${mapstruct-plus.version}</version>
    </dependency>
  	<!--lombok-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>${lombok.version}</version>
      <optional>true</optional>
    </dependency>
</dependencies>

<build>
  <plugins>
    <!-- 解决 MapStruct 和 Lombok 冲突 -->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>${maven-compiler-plugin.version}</version>
      <configuration>
        <source>${java.version}</source>
        <target>${java.version}</target>
        <annotationProcessorPaths>
          <path>
            <groupId>io.github.linpeilie</groupId>
            <artifactId>mapstruct-plus-processor</artifactId>
            <version>${mapstruct-plus.version}</version>
          </path>
          <path>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
          </path>
          <path>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok-mapstruct-binding</artifactId>
            <version>${lombok-mapstruct-binding.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

由于 MapstructPlus 其已经内嵌 Mapstruct,为了防止不同版本之间的差异,请不要再引入 Mapstruct 相关依赖。

# 简单使用

假设有两个类 UserDto 和 User,我们目的是想将 UserDto 的属性转给 User。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String username;
    private int age;
    private boolean young;
}
1
2
3
4
5
6
7
8

UserDto 类上加上 @AutoMapper 注解,然后 target 指定为要转化的对方类

@Data
@AllArgsConstructor
@NoArgsConstructor
@AutoMapper(target = User.class)
public class UserDto {
    private String username;
    private int age;
    private boolean young;
}
1
2
3
4
5
6
7
8
9

此时我们认为 UserDto 是 Source 类,User 是 Target 类。

测试

@SpringBootTest
public class QuickStartTest {

    private static Converter converter = new Converter();

    public static void main(String[] args) {
        UserDto userDto = new UserDto();
        userDto.setUsername("jack");
        userDto.setAge(23);
        userDto.setYoung(false);

        User user = converter.convert(userDto, User.class);
        System.out.println(user);    // UserDto{username='jack', age=23, young=false}

        assert user.getUsername().equals(user.getUsername());
        assert user.getAge() == user.getAge();
        assert user.isYoung() == user.isYoung();

        UserDto newUser = converter.convert(user, UserDto.class);

        System.out.println(newUser);    // User{username='jack', age=23, young=false}

        assert user.getUsername().equals(newUser.getUsername());
        assert user.getAge() == newUser.getAge();
        assert user.isYoung() == newUser.isYoung();
    }
}
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

可以看到,@AutoMapper 注解不仅对 UserDto 到 User 的转换生效,也对 User 到 UserDto 的转换生效。

# 使用指南

MapStructPlus 核心就是 @AutoMapper 注解,下面介绍该注解的更多使用指南。

# 简单转换

要实现两个类之间的转换,只需要在其中一个类上增加注解 @AutoMapper ,配置 target 属性,指定目标类即可。

Car





 





@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = CarDto.class)
public class Car {
    private String name;
    private String type;
}
1
2
3
4
5
6
7
8
9

CarDto

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class CarDto {
    private String name;
    private String type;
}
1
2
3
4
5
6
7
8

测试

@SpringBootTest
public class SimpleConvertTest {

    private Converter converter = new Converter();

    @Test
    public void test() {
        Car car = new Car().setName("宝马").setType("小轿车");
        CarDto carDto = this.converter.convert(car, CarDto.class);
        System.out.println("carDto = " + carDto); // carDto = CarDto(name=宝马, type=小轿车)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

可以看编译后的结果

image-20240106135805517

该例子表示,会生成 Car 转换为 CarDto 的接口 CarToCarDtoMapper 及实现类 CarToCarDtoMapperImpl。在生成的转换代码中,源类型(Car)的所有可读属性将被复制到目标属性类型(CarDto)的相应属性中。

当一个属性与它的目标实体对应物具有相同的名称时,将会被隐式映射。

除此之外,MapstructPlus 会根据当前的默认规则,生成 CarDto 转换为 Car 的接口 CarDtoToCarMapper 及实现类 CarDtoToCarMapperImpl。如果不想生成该转换逻辑的话,可以通过注解的 reverseConvertGenerate 属性来配置。

如:





 





@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = CarDto.class, reverseConvertGenerate = false)
public class Car {
    private String name;
    private String type;
}
1
2
3
4
5
6
7
8
9

这样就不会生成 CartDto 到 Cart 的转换,也就是:

this.converter.convert(carDto, Car.class);
1

不会转换成功。

# 对象的属性自动转换

当要转换的类中,存在自定义类时,会自动寻找该类型的转换方法。

例如,分别有两组对象模型:汽车(Car)和座椅(SeatConfiguration),其中 Car 依赖于 SeatConfiguration。

分别对应对象如下:

Car、SeatConfiguration

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = CarDto.class)
public class Car {
    private String name;
    private String type;
    private SeatConfiguration seatConfiguration;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = SeatConfigurationDto.class)
public class SeatConfiguration {
    private String name;
    private Integer price;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

CarDto、SeatConfigurationDto

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class CarDto {
    private String name;
    private String type;
    private SeatConfigurationDto seatConfiguration;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class SeatConfigurationDto {
    private String name;
    private Integer price;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

测试

@SpringBootTest
public class ObjectConvertTest {

    private Converter converter = new Converter();

    @Test
    public void test() {
        SeatConfiguration seatConfiguration = new SeatConfiguration().setName("真皮座椅").setPrice(5000);
        Car car = new Car().setName("宝马").setType("小轿车").setSeatConfiguration(seatConfiguration);
        CarDto carDto = this.converter.convert(car, CarDto.class);
        System.out.println("carDto = " + carDto); 
        // carDto = CarDto(name=宝马, type=小轿车, seatConfiguration=SeatConfiguration(name=真皮座椅, price=5000))
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 自定义属性转换

当两个类中属性存在不一致的场景时,例如名称、类型等不一致,可以进行自定义转换,通过在属性上面添加 @AutoMapping,来配置映射规则。

# 不同属性名称映射

@AutoMapping 注解中,提供了 target 属性,可以配置当前属性与目标类中 target 属性之间映射。

例如,Book 转换为 BookDto 时,name 属性与 bookName 属性相对应:

Book







 



@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = BookDto.class)
public class Book {
    @AutoMapping(target = "bookName") // target 类的属性名
    private String name;
}
1
2
3
4
5
6
7
8
9

BookDto

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class BookDto {
    private String bookName;
}
1
2
3
4
5
6
7

测试

@SpringBootTest
public class AttributeConvertTest {

    private Converter converter = new Converter();

    @Test
    public void test() {
        Book book = new Book().setName("三国演义");
        BookDto bookDto = this.converter.convert(book, BookDto.class);
        System.out.println(bookDto); // BookDto(bookName=三国演义)
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13

@AutoMapping 注解中还提供 source 方法,该配置默认取当前属性的名称,之所以可以配置,是为了适应一种场景,当前类的某个属性,其内部的属性,转换为目标中的属性字段,则可以通过当前属性来配置。

Goods、Sku








 













@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = GoodsVo.class, reverseConvertGenerate = false)
public class Goods {

    @AutoMapping(source = "sku.price", target = "price") // price 是 GoodsVo 的属性名字
    private Sku sku;

}

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Sku {
    private String name;
    private Integer price;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

GoodsDto

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class GoodsVo {
    private Integer price;
}
1
2
3
4
5
6
7

测试

@SpringBootTest
public class AttributeConvertTest {

    private Converter converter = new Converter();
    
    @Test
    public void test2() {
        Sku sku = new Sku().setName("手机").setPrice(4999);
        Goods goods = new Goods().setSku(sku);
        GoodsVo goodsVo = this.converter.convert(goods, GoodsVo.class);
        System.out.println(goodsVo); // GoodsVo(price=4999)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 指定时间格式转换

当时间类型(例如:Date、LocalDateTime、LocalDate 等等)需要和 String 通过指定时间格式进行转换时,可以通过 @AutoMapping 中的 dateFormat 来配置:

Order

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = OrderEntity.class)
public class Order {

    @AutoMapping(dateFormat = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime orderTime;

    @AutoMapping(dateFormat = "yyyy_MM_dd HH:mm:ss")
    private Date createTime;

    @AutoMapping(target = "orderDate", dateFormat = "yyyy-MM-dd")
    private String date;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

OrderEntity

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = Order.class)
public class OrderEntity {

    @AutoMapping(dateFormat = "yyyy-MM-dd HH:mm:ss")
    private String orderTime;

    @AutoMapping(dateFormat = "yyyy_MM_dd HH:mm:ss")
    private String createTime;

    @AutoMapping(target = "date", dateFormat = "yyyy-MM-dd")
    private LocalDate orderDate;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

测试

@SpringBootTest
public class TimeConvertTest {

    private Converter converter = new Converter();

    @Test
    public void test() {
        Order order = new Order().setOrderTime(LocalDateTime.now()).setCreateTime(new Date()).setDate("2023-07-23");
        OrderEntity orderEntity = this.converter.convert(order, OrderEntity.class);
        System.out.println(orderEntity); // OrderEntity(orderTime=2023-07-23 22:21:00, createTime=2023_07_23 22:21:00, orderDate=2023-07-23)

        OrderEntity orderEntity1 = new OrderEntity().setOrderTime("2023-07-23 22:00:00").setCreateTime("2023_07_23 22:21:00").setOrderDate(LocalDate.now());
        Order order1 = this.converter.convert(orderEntity1, Order.class); // Order(orderTime=2023-07-23T22:00, createTime=Sun Jul 23 22:21:00 CST 2023, date=2023-07-23)
        System.out.println(order1);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 指定数字格式转换

当数字类型(例如:int/Integer 等数字基本类型及包装类、BigDecimal)和 String 之间的转换需要指定数字格式,可以通过 @AutoMapping 的 numberFormat 来配置。

该格式需要 java.text.DecimalFormat 所支持

Order

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = OrderEntity.class)
public class Order {

    @AutoMapping(dateFormat = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime orderTime;

    @AutoMapping(dateFormat = "yyyy_MM_dd HH:mm:ss")
    private Date createTime;

    @AutoMapping(target = "orderDate", dateFormat = "yyyy-MM-dd")
    private String date;

    @AutoMapping(numberFormat = "$0.00")
    private BigDecimal orderPrice;

    @AutoMapping(numberFormat = "$0.00")
    private Integer goodsNum;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

OrderEntity

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = Order.class)
public class OrderEntity {

    @AutoMapping(dateFormat = "yyyy-MM-dd HH:mm:ss")
    private String orderTime;

    @AutoMapping(dateFormat = "yyyy_MM_dd HH:mm:ss")
    private String createTime;

    @AutoMapping(target = "date", dateFormat = "yyyy-MM-dd")
    private LocalDate orderDate;

    @AutoMapping(numberFormat = "$0.00")
    private String orderPrice;

    @AutoMapping(numberFormat = "$0.00")
    private String goodsNum;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

测试

@SpringBootTest
public class NumberConvertTest {

    private Converter converter = new Converter();

    @Test
    public void test() {
        Order order = new Order().setOrderPrice(BigDecimal.valueOf(998.99)).setGoodsNum(10000);
        OrderEntity orderEntity = this.converter.convert(order, OrderEntity.class);
        System.out.println(orderEntity); // OrderEntity(orderTime=null, createTime=null, orderDate=null, orderPrice=$998.99, goodsNum=$10000.00)s
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 忽略指定属性的转换

当在进行转换时,需要忽略指定属性的转换,可以通过 @AutoMapping 的 ignore 来配置。

Car








 




@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = CarDto.class)
public class Car {
    private String name;
    @AutoMapping(ignore = true)
    private String type;
    private SeatConfiguration seatConfiguration;
}
1
2
3
4
5
6
7
8
9
10
11

CarDto

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class CarDto {
    private String name;
    private String type;
    private SeatConfiguration seatConfiguration;
}
1
2
3
4
5
6
7
8
9

测试

@SpringBootTest
public class IgnoreConvertTest {

    private Converter converter = new Converter();

    @Test
    public void test() {
        SeatConfiguration seatConfiguration = new SeatConfiguration().setName("真皮座椅").setPrice(5000);
        Car car = new Car().setName("宝马").setType("小轿车").setSeatConfiguration(seatConfiguration);
        CarDto carDto = this.converter.convert(car, CarDto.class);
        System.out.println(carDto); // CarDto(name=宝马, type=null, seatConfiguration=SeatConfiguration(name=真皮座椅, price=5000))
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 属性转换时的默认值

@AutoMapping 中的 defaultValue 可以指定在转换属性时,当属性为 null 时,转换到目标类中的默认值。

DefaultDto

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = DefaultVo.class)
public class DefaultDto {

    @AutoMapping(defaultValue = "18")
    private Integer i;

    @AutoMapping(defaultValue = "1.32")
    private Double d;

    @AutoMapping(defaultValue = "true")
    private Boolean b;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

DefaultVo

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class DefaultVo {

    private Integer i;

    private Double d;

    private Boolean b;

}
1
2
3
4
5
6
7
8
9
10
11
12
13

测试

@SpringBootTest
public class DefaultValueConvertTest {

    private Converter converter = new Converter();

    @Test
    public void test() {
        DefaultDto defaultDto = new DefaultDto();
        DefaultVo defaultVo = this.converter.convert(defaultDto, DefaultVo.class);
        System.out.println(defaultVo); // DefaultVo(i=18, d=1.32, b=true)
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 表达式

在执行属性转换时,可以通过指定执行一段 Java 代码来进行转换操作,例如,对源对象中的某个属性进行转换后返回。

需要注意的是,在生成时,会直接将表达式插入到转换逻辑中,并不会验证其语法。

例如,将源对象中的 List<String> 属性,通过 , 拼接为字符串:

User










 



@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = UserDto.class)
public class User {
    private String username;
    private int age;
    private boolean young;
    @AutoMapping(target = "educations", expression = "java(java.lang.String.join(\",\", source.getEducationList()))")
    private List<String> educationList;
}
1
2
3
4
5
6
7
8
9
10
11
12

UserDto

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class UserDto {
    private String username;
    private int age;
    private boolean young;
    private String educations;
}
1
2
3
4
5
6
7
8
9
10

测试

@SpringBootTest
public class ExpressionConvertTest {

    private Converter converter = new Converter();

    @Test
    public void test() {
        User user = new User().setUsername("张三").setAge(23).setYoung(true).setEducationList(Arrays.asList("1", "2", "3"));
        UserDto userDto = this.converter.convert(user, UserDto.class);
        System.out.println(userDto); // UserDto(username=张三, age=23, young=true, educations=1,2,3)
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 自定义类型转换器

当不同类型的属性,想要按照自定义的规则进行转换时,可以有两种办法:

  1. 通过 @AutoMapping 中配置的 expression 表达式配置
  2. 自定义一个类型转换器,通过 @AutoMapper 的 uses 属性来引入

方式一可以上面面的 表达式。

这里基于方式二,实现将 String 类型的属性,根据逗号分隔,转换为 List<String> 类型的属性:

首先,定义一个类型转换器:StringToListStringConverter

StringToListStringConverter

@Component
public class StringToListStringConverter {
    public static List<String> stringToListString(String str) {
        return StrUtil.split(str);
    }
}
1
2
3
4
5
6

User

@Data
@AllArgsConstructor
@NoArgsConstructor
@AutoMapper(target = UserDto.class)
public class User {
    private String username;
    private int age;
    private boolean young;
    private List<String> educationList;
}
1
2
3
4
5
6
7
8
9
10

UserDto





 








@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = User.class, uses = StringToListStringConverter.class)
public class UserDto {
    private String username;
    private int age;
    private boolean young;
    @AutoMapping(target = "educationList")
    private String educations;
}
1
2
3
4
5
6
7
8
9
10
11
12

测试

@SpringBootTest
public class CustomerConverterTest {

    private Converter converter = new Converter();

    @Test
    public void test() {
        UserDto userDto = new UserDto().setAge(23).setUsername("张三").setYoung(true).setEducations("1,2,3");

        User user = converter.convert(userDto, User.class);
        System.out.println(user); // User(username=张三, age=23, young=true, educationList=[1, 2, 3])
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 自动接入自定义转换接口

当有的类型转换逻辑比较复杂,可以通过自定义转换接口来实现,即使用 MapStruct 原生的方式。

当使用这种方式时,默认生成的类型转换中,如果有前面提供的类型转换时,会自动引用。

Car

@AutoMapper(target = CarDto.class)
@Data
public class Car {
    private Tyre tyre;
}
1
2
3
4
5

CarDto

@Data
public class CarDto {
    private TyreDTO tyre;
}
1
2
3
4

这里定义 Tyre 和 TyreDTO 之间的转换接口

TyreMapper

@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface TyreMapper {

    TyreDTO tyreToTyreDTO(Tyre tyre);

    Tyre tyreDtoToTyre(TyreDTO tyreDTO);

}
1
2
3
4
5
6
7
8

# 反向属性映射配置

前面前面提到,当在一个类上面添加 @AutoMapper 注解时,默认情况下,除了会生成源类到目标类的转换接口,还会生成目标类到源类的转换接口和实现类,这里需要注意的是,默认情况下生成的该转换接口,并没有任何自定义配置,即使在源类中配置了 @AutoMapping 注解。

这里要实现目标类到源类的自定义转换配置,可以有两种方式:

  1. 在目标类上面添加 @AutoMapper 注解。这是最建议的方式,当转换双方都有添加该注解时,便不会生成默认的转换接口,即按照自定义的规则进行生成
  2. 当目标类访问不到源类,或者项目规范不允许在目标类上面添加该种注解时,可以将自定义配置全部添加在源类中。这就是下面要介绍的 反向属性映射配置

框架中提供了 @ReverseAutoMapping 注解,该注解就是为了配置目标类到源类的自定义转换规则。

注意

这里需要注意的是,防止配置冲突,一旦添加 @ReverseAutoMapping 注解,在目标类中,便不能添加任何自定义转换注解。

@ReverseAutoMapping 注解表示的含义,是目标类到源类转换时,需要指定的自定义转换规则,其中可以配置的属性,与 @AutoMapping 注解一致。

这里有两个属性需要注意,分别是 source 和 target。

这里的 source 指的是目标类中的属性,target 指的是源类中的属性。

能会有人这里有疑问,为什么这里的配置像是反的?如果没有,可以直接跳过。

框架设计的时候,所有的属性转换配置,都是基于要转换的类型,该类转换为目标类,想要应用的效果。这里的 source 也应该是来源类中的属性。

如果还是不理解,这里可以认为,该注解就是本该应用在目标类中的 @AutoMapping 注解,原封不动拷贝到当前类,再修改注解名称即可。

Student 只在源类配置正向和反向转换规则

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@AutoMapper(target = StudentDto.class)
public class Student {
    @AutoMapping(source = "name", target = "sname")
    @ReverseAutoMapping(source = "sname", target = "name")
    private String name;
    @AutoMapping(source = "age", target = "sage")
    @ReverseAutoMapping(source = "sage", target = "age")
    private Integer age;
}
1
2
3
4
5
6
7
8
9
10
11
12
13

StudentDto 在目标类并未配置转换规则

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class StudentDto {
    private String sname;
    private Integer sage;
}
1
2
3
4
5
6
7
8

测试

@SpringBootTest
public class ReverseAutoMappingConvertTest {

    @Resource
    private Converter converter;

    @Test
    public void test() {
        Student student = new Student().setName("张三").setAge(23);
        StudentDto studentDto = this.converter.convert(student, StudentDto.class);
        System.out.println(studentDto); // StudentDto(sname=张三, sage=23)

        StudentDto studentDto1 = new StudentDto().setSname("李四").setSage(18);
        Student student1 = this.converter.convert(studentDto1, Student.class);
        System.out.println(student1); // Student(name=李四, age=18)
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
编辑此页 (opens new window)
#knowledge
更新时间: 2024/01/06, 06:51:53
知识 - MapStruct
知识 - CompletableFuture

← 知识 - MapStruct 知识 - CompletableFuture→

最近更新
01
技术随笔 - Element Plus 修改包名 原创
11-02
02
Reactor - 扩展性
11-02
03
Reactor - 最佳实践
11-02
更多文章>
Theme by Vdoing | Copyright © 2021-2024 Young Kbt | blog
桂ICP备2021009994号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式