在领域模型与持久层的 DO 之间拷贝数据是 java 开发常见的场景, 如果需要拷贝的字段过多, 我们往往寻求使用一些工具以代替赋值语句;
类似 spring 的 BeanUtils 使用反射实现自动赋值是一种经典的解决方案, 但在有性能要求的场景下存在性能瓶颈;
而 mapstruct 提出了另一种解决方案: 使用编译期预处理的方式自动生成拷贝转换的代码, 在节省拷贝代码的同时也保证了转换的性能;

基础映射能力

将一个 java bean 中的字段值映射到另一个 java bean 的同名同类型 (String 与枚举类型也可以相互映射) 的字段上;
如果 source bean 中的字段在 target bean 中没有同名同类型的对应字段, 则该字段无法映射到 target bean;

1
2
3
4
5
@Mapper
public interface TenantTransformer {
TenantTransformer INSTANCE = Mappers.getMapper(TenantTransformer.class);
Tenant toTenant(TenantDO tenantDO);
}

高级用法

Mapping

@org.mapstruct.Mapping 注解可以在两个 bean 的不同名的字段之间建立映射关系;

1
2
3
4
5
6
7
@Mapper
public interface TenantTransformer {
TenantTransformer INSTANCE = Mappers.getMapper(TenantTransformer.class);

@Mapping(target = "name", source = "tenantName")
Tenant toTenant(TenantDO tenantDO);
}

当需要在两个 bean 的不同类型的字段建建立映射关系时, 需要同时配合使用 @org.mapstruct.Named 注解以申明类型转换方法:

1
2
3
4
5
6
7
8
9
10
11
12
@Mapper
public interface TenantTransformer {
TenantTransformer INSTANCE = Mappers.getMapper(TenantTransformer.class);

@Mapping(target = "tagSet", source = "tags", qualifiedByName = "toTagsSet")
Tenant toTenant(TenantDO tenantDO);

@Named("toTagsSet")
static Set<String> toTagsSet(String tagsStr) {
......
}
}

Context

当使用 @Named 注解定义类型转换方法时, 如果需要使用外部的实例时, 可以使用 @org.mapstruct.Context 注解引入外部实例:

1
2
3
4
5
6
7
8
9
10
11
12
@Mapper
public interface TenantTransformer {
TenantTransformer INSTANCE = Mappers.getMapper(TenantTransformer.class);

@Mapping(target = "tagSet", source = "tags", qualifiedByName = "toTagsSet")
Tenant toTenant(TenantDO tenantDO, @Context Plan plan);

@Named("toTagsSet")
static Set<String> toTagsSet(String tagsStr, @Context Plan plan) {
......
}
}

需要同时在主转换方法与 @Named 注解标注的类型转换方法上同时使用 @Context 引入外部实例;

expression

当需要将一个外部的实例映射到 targe bean 的某个字段时, 可以使用 @Mapping 注解的 exprssion 属性:
如下示例的含义是: 将 @Context 申明的外部实例 plan 映射到目标字段 plan 上;

1
2
3
4
5
6
7
@Mapper
public interface TenantTransformer {
TenantTransformer INSTANCE = Mappers.getMapper(TenantTransformer.class);

@Mapping(target = "plan", expression = "java(plan)")
Tenant toTenant(TenantDO tenantDO, @Context Plan plan);
}

具体案例

以下例子涵盖了上述基础映射能力及高级用法:

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
@Mapper
public interface APIMapper {

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

@Mapping(target = "tags", source = "tags", qualifiedByName = "toTagsSet")
@Mapping(target = "devStatus", source = "devStatus", qualifiedByName = "toDevStatus")
@Mapping(target = "trafficDistributions", expression = "java(trafficDistributions)")
@Mapping(target = "trafficGroupInstances", source = "config", qualifiedByName = "toTrafficGroups")
API from(APIDO apiDO, @Context List<TrafficDistribution> trafficDistributions, @Context TrafficGroup.Type type);

@Named("toTagsSet")
static Set<String> toTagsSet(String tagsStr) {
return Sets.newHashSet(ConvertUtil.commaSplit(tagsStr));
}

@Named("toDevStatus")
static Map<Env, API.DevStatus> toDevStatus(String devStatusStr) {
return JSON.parseObject(devStatusStr, new TypeReference<>() {});
}

@Named("toTrafficGroups")
static Map<Env, TrafficGroupInstance> toTrafficGroups(String config, @Context TrafficGroup.Type type) {
final Map<Env, Map<String, Object>> featureConfigByEnv = JSON.parseObject(config, new TypeReference<>() {});
return featureConfigByEnv.entrySet().stream()
.map(entry -> Pair.of(entry.getKey(), deserialize(entry.getValue(), type)))
.collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
}

static TrafficGroupInstance deserialize(Map<String, Object> config, TrafficGroup.Type type) {
final TrafficGroupInstance.TrafficGroupFeatureTransformer<? extends TrafficGroupInstance> transformer =
groupInstanceTransformers.get(type);
return transformer.deserialize(config);
}

@Mapping(target = "tags", source = "tags", qualifiedByName = "serializeTagSet")
@Mapping(target = "devStatus", source = "devStatus", qualifiedByName = "serializeDevStatus")
APIDO from(API api);

@Named("serializeDevStatus")
static String serializeDevStatus(Map<Env, API.DevStatus> devStatus) {
return JSON.toJSONString(devStatus);
}

@Named("serializeTagSet")
static String serializeTagSet(Set<String> tagSet) {
return ConvertUtil.commaJoin(tagSet);
}

}

参考链接