likes
comments
collection
share

深入使用MapStruct

作者站长头像
站长
· 阅读数 13

前言

上一篇我们介绍了如何在java中进行对象的转换:juejin.cn/post/722518…

其中我们重点提到了MapStruct这一工具。今天我们就来深入的了解一下MapStruct在开发中的常用功能。

功能

我们先来创建用于对象转换的两个类:

@Data
public static class UserVO {
    private String fullName;
    private int    age;
    private Extend extend;
}

@Data
public static class User {
    private String name;
    private int    age;
    private Extend extend;
}

@Data
private static class Extend {
    private String email;
    private String address;
    private String phone;
    private String company;
    private String jobTitle;
    private String education;
    private String nationality;
    private String gender;
}

1.普通映射

普通映射是将一个类映射到另外一个类上,使用简单。

如上述代码演示的两个类,我们需要把User中的属性全都复制到UserVO上去。当两个属性的名称不一致时,我们需要配置一下显示的配置一下映射关系,如下: @Mapping (source = "name", target = "fullName")。

    @Mapper
    public interface CommonUserMapper {
        CommonUserMapper INSTANCE = Mappers.getMapper(CommonUserMapper.class);
        @Mapping(source = "name", target = "fullName")
        UserVO toUserVO(User user);
    }

其运行代码如下:UserVO userVO = CommonUserMapper.INSTANCE.toUserVO(user) 。 从运行时变量的结果截图来看,userVO对象中的extend属性和原始User中的extend的对象是同一个,说明我们在不做其他的操作时,

MapStruct对类中的对象属性是浅拷贝。

深入使用MapStruct

要实现深度拷贝有以下办法:

  1. 新建一个和Extend类含有相同属性的新类。比如下图示例,我新建了一个ExtendVO类来进行属性的映射。

深入使用MapStruct

2.参数引用映射

MapStruct允许我们在配置映射关系时,可以配置普通参数。如下示例所示,我们将字符串参数email映射到

UserVO.Extend.email 上:

@Mapping(source = "user.name", target = "fullName")
@Mapping(source = "email", target = "extend.email")
UserVO toUserVO(User user , String email);

这里要注意一下,当配置了参数时,在配置对象属性关系映射时,也要显示的指明对象参数的名称。如这里的

@Mapping(source = "user.name", target = "fullName") 就显示指明了 source = "user.name" 。

运行结果如下入所示:

深入使用MapStruct

3.多对象参数映射

这个映射其实和【参数引用映射】差不多,将多个实体组合成一个输出对象。

如下示例所示,我们将原始Extend对象映射到UserVO.ExtendVO上:

@Mapping(source = "user.name", target = "fullName")
@Mapping(source = "extend", target = "extend")
UserVO toUserVO(User user , Extend extend);

这里的注意点和 【参数引用映射】差不多。

运行结果如下入所示:

深入使用MapStruct

从运行结果,我们看明白一件事:这种方式对于 Extend -> UserVO.ExtendVO 是属于深拷贝的。

4.嵌套释放

比如我们现在的User类,其内嵌了一个Extend类,现在我们需要把UserVO中的ExtendVO展开到UserVO中,变成一个平面类,那么我们改怎么配置呢?

@Mapping(source = "user.name", target = "fullName")
@Mapping(source = "extend", target = ".")
UserVO toUserVO1(User user);

我们使用 “ . as target” ,来实现将一个嵌套的bean的值合并到一个扁平化的对象中。

运行结果如下入所示:

深入使用MapStruct

5.对象更新

上面所演示示例都是使用MapStruct给我们生成了一个对象,那么我们需要更新一个对象属性时该怎么配置呢?在业务场景中,我们可能更需要是只更新某个对象的某些属性,那么又该怎么配置呢?

@Mapping( target = "fullName" ,ignore = true)
@Mapping(source = "extend", target = ".")
void toUserVO2(User user, @MappingTarget UserVO userVO);

深入使用MapStruct

经过测试我们就可以回答上面的问题了,@MappingTarget 使用改注解标记目标对象我们就可以更新该对象的值。但是有一个问题,对象更新的策略和复制是一样的,所以如果你想不更新某个值,可以给加一个 ignore = true的标签来忽略它。

6.集合、流、枚举映射

集合

MapStruct还可以支持集合类型,流,枚举等数据类型的映射关系。

Map示例配置(转换每个值和键的值)请注意一下这里使用的注解是MapMapping

@MapMapping(valueDateFormat = "dd.MM.yyyy") // 数据格式转换
Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);

我们通过反编译的方式可以看到其生成的class文件代码如下:

深入使用MapStruct

流的映射和集合映射类似,MapStruct通过分析自动完成对应的转换逻辑。

如下面的示例(将Integer流转换成字符串集合):

Set<String> integerStreamToStringSet(Stream<Integer> integers);

枚举

集合的转换比较繁琐,而且使用场景有限,用处并不是很大,了解一下即可(枚举的映射关系使用的是ValueMapping)。下面采用一个官方的示例说明:

@ValueMapping(source = "EXTRA", target = "SPECIAL"),
@ValueMapping(source = "STANDARD", target = "DEFAULT"),
@ValueMapping(source = "NORMAL", target = "DEFAULT")
ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);

7.自定义

自定义注解

自定义注解的主要作用是简化我们的配置。比如我们保存和更新时需要两个转换配置(因为复制逻辑可能不一样,比如更新时:id,createTime的属性是不能改变的),但是其他属性的赋值逻辑是一致的,那么我们就可以通过自定义注解的方式来简化这个过程。我们来看一个列子:

@Common
UserVO toSave(User user);

@Mapping(target = "age", ignore = true)
@Common
void toUpdate(User user, @MappingTarget UserVO userVO);

@Retention(RetentionPolicy.CLASS)
@Mapping(source = "user.name", target = "fullName")
@Mapping(source = "extend", target = ".")
public @interface Common {}

深入使用MapStruct

从测试可以看出来和配置可以得出结果:我们自定义的注解被 toSave() 和 toUpdate()继承了,数据符合我们的预期。

自定义表达式

自定义表达式是MapStruct提供的更高级和强大的功能。其可以支持你更灵活的去生成你属性的值。

自定义常量或默认值

  • @Mapping:defaultValue 当源的属性值为null的值,给目标属性赋默认值。
  • @Mapping:constant 直接给目标属性赋值,当使用constant时,无需使用source标签。
  • @Mapping:ignore 可以配置那个属性被忽略,这个在为某些拷贝对象时某些属性不需要赋值时挺好用的。

自定义表达式

  • @Mapping:defaultExpression 当源的属性值为null的值,调用一个表达式来计算一个值给目标属性。
  • @Mapping:expression 给某个属性赋值表达式计算的值,同样当使用expression时,无需使用source标签。

我们来举几个列子:

  • 给person对象的id赋值一个随机UUID。
@Mapping(expression = "java( UUID.randomUUID().toString() )", 
         target = "id")
PersonDto personToDto(Person person);
  • 导入spring容器中的bean,并调用方法获取值并给目标属性赋值。
@Mapper(componentModel = "spring")
public abstract class SimpleMapper {

    @Autowired
    protected SimpleService simpleService;

    @Mapping(target = "name", expression = "java(simpleService.enrichName(source.getName()))")
    public abstract SimpleTarget sourceToTarget(SimpleSource source);
}

表达式非常灵活,大家可以自行多多研究,如果不确定映射关系是怎么样的时候,可以反编译代码查看。

最后:如果大家觉得有用,请点赞关注和收藏。你的鼓励是我更新的最大动力。