MapStruct
是一个代码生成器,它基于约定优于配置方法极大地简化了 Java bean
类型之间映射的实现。自动生成的映射转换代码只使用简单的方法调用,因此速度快、类型安全而且易于理解阅读,源码仓库 Github
地址 MapStruct。总的来说,有如下三个特点:
- 基于注解
- 在编译期自动生成映射转换代码
- 类型安全、高性能、无依赖性
MapStruct 用法
S/T 字段及类型一致
在 Source 和 Target 需要映射字段的字段名和类型一致的场景下,只需写如下的 interface 方法 / abstract 方法即可。
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public abstract class Converter {
public static Converter INSTANT = Mappers.getMapper(Converter.class);
public abstract Target convert(Source source);
}
S/T 字段类型不一致
在 Source 和 Target 需要映射的字段名称或类型不同时,可使用 @Mapping
注解指定映射的 Target 名称及类型。如下,Source 的 Integer 类型的 id,被映射到 Target 中 String 类型的 no。
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public abstract class Converter {
public static Converter INSTANT = Mappers.getMapper(Converter.class);
@Mapping(source = "id", target = "no", resultType = String.class)
public abstract Target convert(Source source);
}
S/T 一对多字段转换
在一些特殊场景下,一个字段中可能包含多个待映射字段的信息。如下,extra 字段包含 id 和 name 两个字段的信息。可以通过定义解析函数的方式读取完成转换。
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;
@Mapper
public abstract class Converter {
public static Converter INSTANT = Mappers.getMapper(Converter.class);
@Mapping(source = "extra", target = "id", qualifiedByName = "extraId")
@Mapping(source = "extra", target = "name", qualifiedByName = "extraName")
public abstract Source convert(Target target);
@Named("extraId")
public String extractId(String extra) {
return extra.split(",")[0];
}
@Named("extraName")
public String extractName(String extra) {
return extra.split(",")[1];
}
}
S/T 多对一字段转换
Target 增加 String 类型的 extra 字段,可以使用自定义转换方法转换,将多个字段内容融合成一个字段。
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;
@Mapper
public abstract class Converter {
public static Converter INSTANT = Mappers.getMapper(Converter.class);
@Mapping(source = "source", target = "extra", qualifiedByName = "convertToExtra")
public abstract Target convert(Source source);
@Named("convertToExtra")
public String convertToExtra(Source source) {
return String.format("%s,%s", source.getId(), source.getName());
}
}
表达式转换
根据内嵌代码执行转换
@Mapping(target = "name", expression = "java(programer.getName().toUpperCase())")
日期转换
将日期类型转换为指定格式字符串。
@Mapping(target = "beDate", dateFormat = "yyyy-MM-dd HH:mm:ss")
数值转换
将数值类型转换为指定格式字符串。
@Mapping(target = "height", source = "height" ,numberFormat = "#.00")
映射切面
MapStruct 还提供了两个注解 @BeforeMapping
、@AfterMapping
用来实现在 mapping 前后的统一操作,常用于操作多态。
实例:Human 父类,有 Man 和 Woman 两个子类,然后我们要将这两个子类型 mapping 成 HumanDto。HumanDto 中有个性别的属性,需要根据具体的类型决定。在 mapping 完成后,我们还要将名称修饰一下。
public class Human {
private String name;
}
public class Man extends Human{
}
public class Woman extends Human{
}
public class HumanDto {
private String name;
private GenderType genderType;
}
public enum GenderType {
MAN, WOMAN
}
当然我们可以写两个转换方法,一个 Man 到 HumanDto,一个 Woman 到 HumanDto,但是在使用的时候就比较麻烦了,需要传入具体的类型,代码也有重复。这种场景下我们就可以使用这两个注解完美的解决问题。
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public abstract class HumanConvertor {
@BeforeMapping
protected void humanDtoWithGender(Human human, @MappingTarget HumanDto humanDto) {
if (human instanceof Man) {
humanDto.setGenderType(GenderType.MAN);
} else if (human instanceof Woman) {
humanDto.setGenderType(GenderType.WOMAN);
}
}
@AfterMapping
protected void decorateName(@MappingTarget HumanDto humanDto) {
humanDto.setName(String.format("【%s】", humanDto.getName()));
}
public abstract HumanDto toHumanDto(Human human);
}
更新对象属性
MapStruct 提供了另外一种方式来更新一个对象中的属性。
@Mappings({
@Mapping(source = "id", target = "userId"),
@Mapping(source = "username", target = "name"),
@Mapping(source = "role.roleName", target = "roleName")
})
void update(User user, @MappingTarget UserRoleDto userRoleDto);
通过 @MappingTarget 来指定目标类(谁的属性需要被更新)。@Mapping 还是用来定义属性对应规则。
自定义类型转换
定义转换器
public class BoolStrFormat {
public String toStr(Boolean isDisable) {
if (isDisable) {
return "Y";
} else {
return "N";
} }
public Boolean toBool(String str) {
if ("Y".equals(str)) {
return true;
} else {
return false;
}
}
}
定义映射器
@Mapper(uses = {BoolStrFormat.class})
public abstract class Converter {
public static Converter INSTANT = Mappers.getMapper(Converter.class);
public abstract Target convert(Source source);
}
这样转换时,Boolean 类型会通过 BoolStrFormat 被转换成特定字符串。
为转换加缓存
使用 ThreadLocal 缓存 extra 解析结果。
其他特性
多数据源
多数据源场景下,需显示指定参数名称
@Mapping(target = "boyName", source = "boy.name")
@Mapping(target = "girlName", source = "girl.name")
Couple toCouple(Boy boy, Girl girl);
注入外部依赖
@Mapper
注解有 use 和 import 两种注入方式。import 指通过生成 @Autowire 注解注入指定的类去使用。use 指通过 new 指定对象的实例进行使用。
忽略映射
@Mapping(target = "name", ignore = true)
设置默认值
source 值为 null 时给一个默认值。
@Mapping(target = "name", defaultValue = "Amy")
设置常量
直接给 target 值设置一个常量,而不是从 source 进行转换。
@Mapping(target = "name", constant = "Sam")