MapStruct 是一种代码生成器,它极大地简化了基于 “约定优于配置” 方法的 Java Bean 类型之间映射的实现。相对于其他 Bean 转换工具,MapStruct 生成的映射代码使用纯方法调用,因此快速、类型安全且易于理解。

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.mapstruct</groupId>
  4. <artifactId>mapstruct</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.mapstruct</groupId>
  8. <artifactId>mapstruct-processor</artifactId>
  9. </dependency>
  10. </dependencies>

基础使用示例

有如下两个实体类,代码中省略了 Getter、Setter 方法:

  1. public class PersonDO {
  2. private Integer id;
  3. private String name;
  4. private int age;
  5. private Date birthday;
  6. }
  7. public class PersonDTO {
  8. private Integer id;
  9. private String userName;
  10. private Integer age;
  11. private Date birthday;
  12. }

现通过 MapStruct 对这两个实体类进行数据转换:

  1. // @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则
  2. // 在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制
  3. @Mapper
  4. public interface PersonConverter {
  5. PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
  6. @Mappings(@Mapping(source = "name", target = "userName"))
  7. PersonDTO do2dto(PersonDO person);
  8. }

测试结果如下:

  1. public static void main(String[] args) {
  2. PersonDO personDO = new PersonDO();
  3. personDO.setId(1);
  4. personDO.setName("xl");
  5. personDO.setAge(25);
  6. personDO.setBirthday(new Date());
  7. PersonDTO personDTO = PersonConverter.INSTANCE.do2dto(personDO);
  8. }

MapStruct 能够进行实体转换的原因,就是在编译期会自动生成一个实现了 PersonConverter 接口的子类,并为转换方法生成对应的 Setter、Getter 赋值代码。由于在编译期做了很多事情,所以 MapStruct 在运行期的性能会很好,并且还有一个好处,那就是可以把问题的暴露提前到编译期。使得如果代码中字段映射有问题,那么应用就会无法编译,强制开发者要解决这个问题才行。

1. @Mapping

通常情况下,MapStruct 会对属性名相同的字段自动做映射,不需要做额外配置,其中包括:

  • 基本类型及其包装类型,如 int 和 Integer
  • 基本类型的包装类型和 String 类型,如 String 和 Integer
  • String 类型和枚举类型

但如果属性名称不同,则可以通过 @Mapping 注解的 source target 来手动指定字段的映射逻辑:

  1. @Mapping(source = "name", target = "userName")

如果想要为某个属性定义一个常量值,则可以使用 constant

  1. @Mapping(source = "name", constant = "xl")

如果属性类型不匹配,比如一个是 JSON 字符串,一个是实体对象。此时,我们还可以通过 expression 手动设置转换的自定义逻辑,不过目前只支持 Java 代码:

  1. @Mapper
  2. interface PersonConverter {
  3. PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
  4. @Mapping(target = "address",expression = "java(addressToString(dto2do.getAddress()))")
  5. PersonDO dto2do(PersonDTO dto2do);
  6. default String addressToString(Address address){
  7. return JSON.toJSONString(address);
  8. }
  9. }

上面这种是自定义的类型转换,还有一些类型的转换是 MapStruct 本身就支持的,如 String 和 Date 之间的转换,MapStruct 底层会使用 SimpleDateFormat 按照 dateFormat 对其格式化:

  1. @Mapping(target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")

如果想要忽略某个属性的赋值,则可以将 ignore 设为 true:

  1. @Mapping(target = "id", ignore = true)

2. Map 转 Java Bean

如果有以下 Java Bean,省略 Setter、Getter 方法:

  1. public class Customer {
  2. private Long id;
  3. private String name;
  4. }

我们可以编写如下转换方法:

  1. @Mapper
  2. public interface CustomerMapper {
  3. @Mapping(target = "name", source = "customerName")
  4. Customer toCustomer(Map<String, String> map);
  5. }

最终会生成类似如下的转换代码:

  1. public class CustomerMapperImpl implements CustomerMapper {
  2. @Override
  3. public Customer toCustomer(Map<String, String> map) {
  4. Customer customer = new Customer();
  5. if ( map.containsKey( "id" ) ) {
  6. customer.setId( Integer.parseInt( map.get( "id" ) ) );
  7. }
  8. if ( map.containsKey( "customerName" ) ) {
  9. customer.setName( source.get( "customerName" ) );
  10. }
  11. }
  12. }

不过需要注意,待转换的 Map 的 key 必须是 String 类型的,否则 MapStruct 转换代码会跳过这个 key。

3. 子类转换

假如有父类 Fruit 和两个子类 Apple 和 Banana 及其对应的 DTO 类,MapStruct 还支持以下转换代码:

  1. @Mapper
  2. public interface FruitMapper {
  3. @SubclassMapping( source = AppleDto.class, target = Apple.class )
  4. @SubclassMapping( source = BananaDto.class, target = Banana.class )
  5. Fruit map( FruitDto source );
  6. }

注意,如果 Fruit 是抽象类或者是接口,则编译会错误。

4. 使用 Spring 依赖注入

通过在 @Mapper 注解上指定 componentModel 属性为 spring 即可将实现类作为 Spring Bean 注入到容器中,这样在其他地方可以直接通过依赖注入使用。

  1. @Mapper(componentModel = "spring")
  2. public interface CustomerMapper {
  3. @Mapping(source = "name", target = "customerName")
  4. CustomerDto toCustomerDto(Customer customer);
  5. }

看一下由 MapStruct 自动生成的类文件,会发现标记了 @Component 注解。

  1. @Component
  2. public class CustomerMapperImpl implements CustomerMapper {
  3. //...
  4. }