⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/qq_30231473/article/details/129782881 「红兔哥」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

Java项目中实体转换无处不在,当实体字段较多或者大批量的进行复制时,通过手工setter/getter显得太LOW,同时兼备高性能要求情况下,MapStruct完全完全能够胜任。

官方解释,MapStruct是一个代码生成器,它基于约定优于配置的方法,极大地简化了Java bean类型之间映射的实现。生成的映射代码使用普通方法调用,因此快速、类型安全且易于理解。因为MapStruct是在编译期间生成setter/getter方法,实际运行时就是直接调用setter/getter,效率会非常高。

1优点

  • MapStruct编译期生成映射代码,所以可以在编译时暴露映射错误的代码,让错误提前暴露;
  • 因为使用setter/getter方式,而非反射方式,所以可以更快的执行效率;
  • 可以实现深拷贝,自动类型转换,如枚举转换;
  • 进行自定义的映射,多种映射方式,下边具体说明;

2性能对比

对比对象

10个对象复制1次

1万个对象复制1次

100万个对象复制1次

100万个对象复制5次

MapStruct

0ms

3ms

96ms

281ms

Hutools的BeanUtil

23ms

102ms

1734ms

8316ms

Spring的BeanUtils

2ms

47ms

726ms

3676ms

Apache的BeanUtils

20ms

156ms

10658ms

52355ms

Apache的PropertyUtils

5ms

68ms

6767ms

30694ms

3使用

依赖

<!-- MapStruct核心,包含了一些必要的注解-->
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.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编译,注解处理器,根据注解自动生成Mapper的实现 -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

定义转换接口

/**
* 测试接口
*
* @author reboot
*/
@Mapper
public interface OrderConvertor {

/**
* 实例
*/
OrderConvertor INSTANCE = Mappers.getMapper(OrderConvertor.class);

/**
* OrderDo -> OrderModel
*
* @param orderDo 订单实体
* @return {@link OrderModel}
*/
OrderModel toModel(OrderDo orderDo);

/**
* OrderDo -> OrderModel
*
* @param orderDos 订单实体
* @return {@link OrderModel}
*/
List<OrderModel> toModel(List<OrderDo> orderDos);

/**
* OrderModel -> OrderDo
*
* @param orderModel 订单模型
* @return {@link OrderDo}
*/
OrderDo toDo(OrderModel orderModel);

/**
* OrderModel -> OrderDo
*
* @param orderModels 订单模型
* @return {@link OrderDo}
*/
List<OrderDo> toDo(List<OrderModel> orderModels);
}

编译结果

MapStruct会自动生成对应接口的实现,并自动完成属性映射关系,List会自动进行批量处理。

调用

/**
* 订单服务
*
* @author reboot
*/
@Service
public class OrderService {

/**
* 获取订单列表
*
* @return {@link List}<{@link OrderModel}>
*/
public List<OrderModel> getOrderList() {
// 获取数据库数据DO
List<OrderDo> result = selectOrderList();
// 参数转换
return OrderConvertor.INSTANCE.toModel(result);
}
}

4插件

上边的使用方式虽然能够正常使用,但是在一些属性配置映射上和提示上,如果使用插件能够提升使用体验,IDEA中可以直接安装Mapstruct Support插件,当然Eclipse也有对应的插件。

特性

  • 突出显示目标属性和源属性。将目标属性和源属性转到声明的setter / getter中;

  • 错误和快速修复:

    • 缺少@Mapper或@MapperConfig注解检查;
    • 快速修复未映射的目标属性,添加未映射目标属性和忽略未映射目标属性;

5其他用法

更加详细的内容可以查看官方文档,发布文章时最新版本是 MapStruct 1.5.3.Final.html。

基础映射

@Mapper
public interface CarMapper {

@Mapping(target = "manufacturer", source = "make")
@Mapping(target = "seatCount", source = "numberOfSeats")
CarDto carToCarDto(Car car);

@Mapping(target = "fullName", source = "name")
PersonDto personToPersonDto(Person person);
}

target表示目标属性名,source表示源属性名,一般在目标属性和源属性不同时使用,相同的属性名会自动进行映射。

映射器添加自定义方法

@Mapper
public interface CarMapper {

@Mapping(...)
...
CarDto carToCarDto(Car car);

default PersonDto personToPersonDto(Person person) {
//hand-written mapping logic
}
}

自定义方法personToPersonDto并实现,在生成的实现类中会进行覆盖使用。

多个源参数映射

@Mapper
public interface AddressMapper {

@Mapping(target = "description", source = "person.description")
@Mapping(target = "houseNumber", source = "address.houseNo")
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);

@Mapping(target = "description", source = "person.description")
@Mapping(target = "houseNumber", source = "hn")
DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Integer hn);
}

存在多个源参数,使用参数名.属性名的方式进行表示,也可以直接使用基础类型的属性名称。

嵌套属性映射到当前目标

@Mapper
public interface CustomerMapper {

@Mapping( target = "name", source = "record.name" )
@Mapping( target = ".", source = "record" )
@Mapping( target = ".", source = "account" )
Customer customerDtoToCustomer(CustomerDto customerDto);
}

当源参数中存在对象属性,可以手动进行映射,或者直接使用”.”的方式将对象中的属性全部映射到当前目标对象。

表达式方式

@Mapper
public interface SourceTargetMapper {

SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );

@Mapping(
target = "timeAndFormat",
expression = "java( new org.sample.TimeAndFormat( s.getTime(), s.getFormat() ) )"
)
Target sourceToTarget(Source s);
}

支持使用java代码块进行转换,一般可以将静态方法处理的字段放到这里。

更新现有实例

@Mapper
public interface CarMapper {

void updateCarFromDto(CarDto carDto, @MappingTarget Car car);
}

@MappingTarget源参数,编译时会将carDto参数中的属性映射到car参数中。

Map映射

@Mapper
public interface CustomerMapper {

@Mapping(target = "name", source = "customerName")
Customer toCustomer(Map<String, String> map);
}

直接将map中的key进行映射。

更多用法

还有更多其他用法,比如:

  • 支持映射定义的public属性;
  • 支持映射参数Builder模式;
  • 使用注入方式引入转换器;
  • 数据类型字段转换,如枚举、日期,支持日期格式化,支持数字类型格式化
  • 集合类型自动转换;
  • 转换Stream;

6总结

MapStruct还有很多其他高阶特性,限于篇幅文章仅仅列举部分示例,有兴趣的同学可以查看对应文档试试。使用适当的工具有效提高编程效率,在使用工具过程中我们也了解其实现原理,不断提高自身。

文章目录
  1. 1. 1优点
  2. 2. 2性能对比
  3. 3. 3使用
    1. 3.1. 依赖
    2. 3.2. 定义转换接口
    3. 3.3. 编译结果
    4. 3.4. 调用
  4. 4. 4插件
    1. 4.1. 特性
  5. 5. 5其他用法
    1. 5.1. 基础映射
    2. 5.2. 映射器添加自定义方法
    3. 5.3. 多个源参数映射
    4. 5.4. 嵌套属性映射到当前目标
    5. 5.5. 表达式方式
    6. 5.6. 更新现有实例
    7. 5.7. Map映射
    8. 5.8. 更多用法
  6. 6. 6总结