两个类之间的转换

linpeilie

简单转换

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

例如:

@AutoMapper(target = CarDto.class)
public class Car {
    // ...
}

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

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

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

自定义对象的属性自动转换

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

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

分别对应对象如下:

  • 汽车模型
@AutoMapper(target = CarDto.class)
@Data
public class Car {
    private SeatConfiguration seatConfiguration;
}
@Data
public class CarDto {
    private SeatConfigurationDto seatConfiguration;
}
  • 座椅模型
@Data
@AutoMapper(target = SeatConfigurationDto.class)
public class SeatConfiguration {
    // fields
} 
@Data
public class SeatConfigurationDto {
    // fields
} 

在上面的例子中,首先会生成 CarToCarDtoMapperSeatConfigurationToSeatConfigurationDtoMapper 两个转换接口,并且在转换 Car 时,会自动使用 SeatConfigurationToSeatConfigurationDtoMapper 来对其中的座椅属性来进行转换。

引入自定义类型转换器

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

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

方式一可以参考下面的表达式章节。

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

首先,定义一个类型转换器 —— StringToListString

@Component
public class StringToListString {
    public List<String> stringToListString(String str) {
        return StrUtil.split(str);
    }
}

WARNING

  • 类型转换器提供的类型转换方法,可以定义为 staticnonstatic的。
  • 如果是基于 SpringBoot 的方式使用该框架,则类型转换器需要定义为 Spring 的一个 Bean。

下一步,使用该类型转换器:

@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;
    // ......
}

测试:

@SpringBootTest
public class QuickStartTest {

    @Autowired
    private Converter converter;

    @Test
    public void ueseTest() {
        UserDto userDto = new UserDto();
        userDto.setEducations("1,2,3");

        final User user = converter.convert(userDto, User.class);
        System.out.println(user.getEducationList());    // [1, 2, 3]

        assert user.getEducationList().size() == 3;
    }
}

当自定的类型转换器中有多个方法时,还可以通过 @AutoMappingqualifiedByName 来指定具体的转换方法。 具体可以参考 指定转换方法 章节。

自定义属性转换

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

不同属性名称映射

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

例如,Car 转换为 CatDto 时,seatConfiguration 属性与 seat 属性相对应:

@AutoMapper(target = CarDto.class)
@Data
public class Car {
    @AutoMapping(target = "seat")
    private SeatConfiguration seatConfiguration;
}

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

例如:

@Data
@AutoMapper(target = GoodsVo.class, reverseConvertGenerate = false)
public class Goods {

    @AutoMapping(source = "sku.price", target = "price")
    private Sku sku;

}
@Data
public class GoodsVo {

    private Integer price;

}

指定时间格式转换

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

例如:

@Data
@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;

}
@Data
@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;

}

指定数字格式转换

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

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

例如:

@Data
@AutoMapper(target = OrderEntity.class)
public class Order {

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

    @AutoMapping(numberFormat = "$0.00")
    private Integer goodsNum;
    
}
@Data
@AutoMapper(target = Order.class)
public class OrderEntity {

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

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

忽略指定属性的转换

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

例如:

@AutoMapper(target = CarDto.class)
@Data
public class Car {

    @AutoMapping(target = "wheels", ignore = true)
    private Wheels wheels;
    
} 

属性转换时的默认值

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

例如:

@Data
@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;

}

表达式

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

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

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

@AutoMapper(target = UserDto.class)
public class User {

    @AutoMapping(target = "educations", expression = "java(java.lang.String.join(\",\", source.getEducationList()))")
    private List<String> educationList;

}

指定转换方法

INFO

since 1.4.0
需要注意的是,该功能需要结合 @AutoMapperuses 一起使用。

当某个属性需要单独定义转换逻辑,并且比较复杂时,可以先实现该方法,通过 qualifiedByName 来指定该方法。

例如:

需要电影发行,需要根据不同的语言,修改为相应的语言标题。这里先实现两个转换方法,一个是将英语转为法语,另一个是将法语转换为英语:

@Component
@Named("TitleTranslator")
public class Titles {

    @Named("EnglishToFrench")
    public String translateTitleEF(String title) {
        if ("One Hundred Years of Solitude".equals(title)) {
            return "Cent ans de solitude";
        }
        return "Inconnu et inconnu";
    }

    @Named("FrenchToEnglish")
    public String translateTitleFE(String title) {
        if ("Cent ans de solitude".equals(title)) {
            return "One Hundred Years of Solitude";
        }
        return "Unknown";
    }

}

接下来应用该转换逻辑:

@Data
@AutoMapper(target = FrenchRelease.class, uses = Titles.class)
public class EnglishRelease {

    @AutoMapping(qualifiedByName = "EnglishToFrench")
    private String title;

}
@Data
@AutoMapper(target = EnglishRelease.class, uses = Titles.class)
public class FrenchRelease {

    @AutoMapping(qualifiedByName = "FrenchToEnglish")
    private String title;

}

指定属性之间的依赖关系

当属性 A 的转换逻辑,依赖于属性 B,可以指定 A 依赖 B,则在进行转换时,会先转换 B,然后再转换 A。

示例:

@Data
@AutoMapper(target = DependsTarget.class)
public class DependsSource {

    private String firstName;
    private String lastName;
    @AutoMapping(dependsOn = {"firstName", "lastName"})
    private String fullName;

}
@Data
public class DependsTarget {

    private String firstName;
    private String lastName;
    private String fullName;

}

自动接入自定义转换接口

INFO

since 1.2.3

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

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

例如:

@AutoMapper(target = CarDto.class)
@Data
public class Car {
    private Tyre tyre;
}
@Data
public class CarDto {
    private TyreDTO tyre;
}

这里定义 TyreTyreDTO 之间的转换接口:

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

    TyreDTO tyreToTyreDTO(Tyre tyre);

    Tyre tyreDtoToTyre(TyreDTO tyreDTO);

}

生成的 CarCarDto 转换接口的实现类如下:

反向属性映射配置

INFO

在该文中,所有提到的源类指通过 @AutoMapper 注解的类;目标类指的是 @AutoMappertarget 属性指定的类型。

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

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

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

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

WARNING

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

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

这里有两个属性需要注意,分别是 sourcetarget

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

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

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

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

不可变类型设计

since 1.3.2

当一个类型是不可变类型时,之前默认的规则,生成的 T convert(S source, @MappingTarget T target) 可能会存在问题。

所以,可以使用任意包下的 Immutable 注解,标识一个类为不可变类型, 当为不可变类型时,@MappingTarget 没有意义,上面的方法最终生成如下:

public T convert(S source, @MappingTarget T target) {
    return target;
}
Last Updated 3/16/2024, 2:22:17 PM