在领域模型与持久层的 DO 之间拷贝数据是 java 开发常见的场景, 如果需要拷贝的字段过多, 我们往往寻求使用一些工具以代替赋值语句;
类似 spring 的 BeanUtils 使用反射实现自动赋值是一种经典的解决方案, 但在有性能要求的场景下存在性能瓶颈;
而 mapstruct 提出了另一种解决方案: 使用编译期预处理的方式自动生成拷贝转换的代码, 在节省拷贝代码的同时也保证了转换的性能;
基础映射能力
将一个 java bean 中的字段值映射到另一个 java bean 的同名同类型 (String 与枚举类型也可以相互映射) 的字段上;
如果 source bean 中的字段在 target bean 中没有同名同类型的对应字段, 则该字段无法映射到 target bean;1
2
3
4
5
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
public interface TenantTransformer {
TenantTransformer INSTANCE = Mappers.getMapper(TenantTransformer.class);
"name", source = "tenantName") (target =
Tenant toTenant(TenantDO tenantDO);
}
当需要在两个 bean 的不同类型的字段建建立映射关系时, 需要同时配合使用 @org.mapstruct.Named
注解以申明类型转换方法:1
2
3
4
5
6
7
8
9
10
11
12
public interface TenantTransformer {
TenantTransformer INSTANCE = Mappers.getMapper(TenantTransformer.class);
"tagSet", source = "tags", qualifiedByName = "toTagsSet") (target =
Tenant toTenant(TenantDO tenantDO);
"toTagsSet") (
static Set<String> toTagsSet(String tagsStr) {
......
}
}
Context
当使用 @Named 注解定义类型转换方法时, 如果需要使用外部的实例时, 可以使用 @org.mapstruct.Context
注解引入外部实例:1
2
3
4
5
6
7
8
9
10
11
12
public interface TenantTransformer {
TenantTransformer INSTANCE = Mappers.getMapper(TenantTransformer.class);
"tagSet", source = "tags", qualifiedByName = "toTagsSet") (target =
Tenant toTenant(TenantDO tenantDO, @Context Plan plan);
"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
public interface TenantTransformer {
TenantTransformer INSTANCE = Mappers.getMapper(TenantTransformer.class);
"plan", expression = "java(plan)") (target =
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
public interface APIMapper {
APIMapper INSTANCE = Mappers.getMapper(APIMapper.class);
"tags", source = "tags", qualifiedByName = "toTagsSet") (target =
"devStatus", source = "devStatus", qualifiedByName = "toDevStatus") (target =
"trafficDistributions", expression = "java(trafficDistributions)") (target =
"trafficGroupInstances", source = "config", qualifiedByName = "toTrafficGroups") (target =
API from(APIDO apiDO, @Context List<TrafficDistribution> trafficDistributions, @Context TrafficGroup.Type type);
"toTagsSet") (
static Set<String> toTagsSet(String tagsStr) {
return Sets.newHashSet(ConvertUtil.commaSplit(tagsStr));
}
"toDevStatus") (
static Map<Env, API.DevStatus> toDevStatus(String devStatusStr) {
return JSON.parseObject(devStatusStr, new TypeReference<>() {});
}
"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);
}
"tags", source = "tags", qualifiedByName = "serializeTagSet") (target =
"devStatus", source = "devStatus", qualifiedByName = "serializeDevStatus") (target =
APIDO from(API api);
"serializeDevStatus") (
static String serializeDevStatus(Map<Env, API.DevStatus> devStatus) {
return JSON.toJSONString(devStatus);
}
"serializeTagSet") (
static String serializeTagSet(Set<String> tagSet) {
return ConvertUtil.commaJoin(tagSet);
}
}