公众号关注 “程序员虎哥”
设为 “星标”,带你学习更多知识!
前言
按照日常开发习惯,对于不同领域层使用不同JavaBean对象传输数据,避免相互影响,因此基于数据库实体对象User衍生出比如UserDto、UserVo等对象,于是在不同层之间进行数据传输时,不可避免地需要将这些对象进行互相转换操作。常见的转换方式有:- 调用getter/setter方法进行属性赋值
- 调用BeanUtil.copyPropertie进行反射属性赋值
第一种方式不必说,属性多了就需要写一大坨getter/setter代码。第二种方式比第一种方式要简便很多,但是坑巨多,比如sources与target写反,难以定位某个字段在哪里进行的赋值,同时因为用到反射,导致性能也不佳。
鉴于此,今天写一写第三种对象转换方式,本文使用的是 MapStruct 工具进行转换,MapStruct 原理也很简单,就是在代码编译阶段生成对应的赋值代码,底层原理还是调用getter/setter方法,但是这是由工具替我们完成,MapStruct在不影响性能的情况下,解决了前面两种方式弊端,很赞~准备工作
为了讲解 MapStruct 工具的使用,本文使用常见的 User 类以及对应 UserDto 对象来演示。@Data@Accessors(chain = true)public class User {private Long id;private String username;private String password; private Integer sex; private LocalDate birthday; private LocalDateTime createTime; private String config; private String test;
}@Data@Accessors(chain = true)public class UserVo {private Long id;private String username;private String password;private Integer gender;private LocalDate birthday;private String createTime;private Listconfig;private String test; @Datapublic static class UserConfig {private String field1;private Integer field2;
}
}
注意观察这两个类的区别。一、MapStruct 配置以及基础使用
项目中引入 MapStruct 的依赖<dependency><groupId>org.mapstructgroupId>
<artifactId>mapstructartifactId>
<version>1.3.1.Finalversion>
dependency>
<dependency>
<groupId>org.mapstructgroupId>
<artifactId>mapstruct-processorartifactId>
<version>1.3.1.Finalversion>
dependency>
因为项目中的对象转换操作基本都一样,因此抽取除了一个转换基类,不同对象如果只是简单转换可以直接继承该基类,而无需覆写基类任何方法,即只需要一个空类即可。如果子类覆写了基类的方法,则基类上的 @Mapping 会失效。@MapperConfig
public interface BaseMapping{/**
* 映射同名属性
*/
@Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
TARGET sourceToTarget(SOURCE var1);/**
* 反向,映射同名属性
*/
@InheritInverseConfiguration(name = "sourceToTarget")
SOURCE targetToSource(TARGET var1);/**
* 映射同名属性,集合形式
*/
@InheritConfiguration(name = "sourceToTarget")
ListsourceToTarget(Listvar1);/**
* 反向,映射同名属性,集合形式
*/
@InheritConfiguration(name = "targetToSource")
ListtargetToSource(Listvar1);/**
* 映射同名属性,集合流形式
*/List<TARGET> sourceToTarget(Stream<SOURCE> stream);/**
* 反向,映射同名属性,集合流形式
*/List<SOURCE> targetToSource(Stream<TARGET> stream);
}
实现 User 与 UserVo 对象的转换器import org.mapstruct.Mapper;import org.mapstruct.Mapping;@Mapper(componentModel = "spring")public interface UserMapping extends BaseMapping<User, UserVo> {@Mapping(target = "gender", source = "sex")@Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")@OverrideUserVo sourceToTarget(User var1);@Mapping(target = "sex", source = "gender")@Mapping(target = "password", ignore = true)@Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")@OverrideUser targetToSource(UserVo var1);default ListstrConfigToListUserConfig(String config) {return JSON.parseArray(config, UserConfig.class);
}default String listUserConfigToStrConfig(Listlist) {return JSON.toJSONString(list);
}
}
本文示例使用的是 Spring 的方式,@Mapper 注解的 componentModel 属性值为 spring,不过应该大多数都用的此模式进行开发。@Mapping用于配置对象的映射关系,示例中 User 对象性别属性名为 sex,而UserVo对象性别属性名为gender,因此需要配置 target 与 source 属性。password 字段不应该返回到前台,可以采取两种方式不进行转换,第一种就是在vo对象中不出现password字段,第二种就是在@Mapping中设置该字段 ignore = true。MapStruct 提供了时间格式化的属性 dataFormat,支持Date、LocalDate、LocalDateTime等时间类型与String的转换。示例中birthday 属性为 LocalDate 类型,可以无需指定dataFormat自动完成转换,而LocalDateTime类型默认使用的是ISO格式时间,在国内往往不符合需求,因此需要手动指定一下 dataFormat。二、自定义属性类型转换方法
一般常用的类型字段转换 MapStruct都能替我们完成,但是有一些是我们自定义的对象类型,MapStruct就不能进行字段转换,这就需要我们编写对应的类型转换方法,笔者使用的是JDK8,支持接口中的默认方法,可以直接在转换器中添加自定义类型转换方法。示例中User对象的config属性是一个JSON字符串,UserVo对象中是List类型的,这需要实现JSON字符串与对象的互转。default ListstrConfigToListUserConfig(String config) {return JSON.parseArray(config, UserConfig.class);
}default String listUserConfigToStrConfig(Listlist) {return JSON.toJSONString(list);
}
如果是 JDK8以下的,不支持默认方法,可以另外定义一个 转换器,然后再当前转换器的 @Mapper 中通过 uses = XXX.class 进行引用。定义好方法之后,MapStruct当匹配到合适类型的字段时,会调用我们自定义的转换方法进行转换。三、单元测试
@Slf4j@RunWith(SpringRunner.class)@SpringBootTestpublic class MapStructTest {@Resourceprivate UserMapping userMapping;@Testpublic void tetDomain2DTO() {
User user = new User()
.setId(1L)
.setUsername("zhangsan")
.setSex(1)
.setPassword("abc123")
.setCreateTime(LocalDateTime.now())
.setBirthday(LocalDate.of(1999, 9, 27))
.setConfig("[{\"field1\":\"Test Field1\",\"field2\":500}]");
UserVo userVo = userMapping.sourceToTarget(user);
log.info("User: {}", user);
log.info("UserVo: {}", userVo);
}@Testpublic void testDTO2Domain() {
UserConfig userConfig = new UserConfig();
userConfig.setField1("Test Field1");
userConfig.setField2(500);
UserVo userVo = new UserVo()
.setId(1L)
.setUsername("zhangsan")
.setGender(2)
.setCreateTime("2020-01-18 15:32:54")
.setBirthday(LocalDate.of(1999, 9, 27))
.setConfig(Collections.singletonList(userConfig));
User user = userMapping.targetToSource(userVo);
log.info("UserVo: {}", userVo);
log.info("User: {}", user);
}
四、常见问题
当两个对象属性不一致时,比如User对象中某个字段不存在与UserVo当中时,在编译时会有警告提示,可以在@Mapping中配置 ignore = true,当字段较多时,可以直接在@Mapper中设置unmappedTargetPolicy属性或者unmappedSourcePolicy属性为 ReportingPolicy.IGNORE即可。如果项目中也同时使用到了 Lombok,一定要注意 Lombok的版本要等于或者高于1.18.10,否则会有编译不通过的情况发生,笔者掉进这个坑很久才爬了出来,希望各位不要重复踩坑。代码下载
本文涉及代码已上传到 Github,以供参考。https://github.com/Mosiki/learning-modules/tree/master/learning-mapstruct参考
https://mapstruct.org/documentation/stable/reference/html/
https://mapstruct.org/faq/
https://github.com/mapstruct/mapstruct-examples
http://www.kailing.pub/MapStruct1.3/index.html
https://www.cnblogs.com/javaguide/p/11861749.html
阅读推荐
一天看10000张黄图,鉴黄师的苦!!!
这几款工具不火,天理难容!!
抖音创始人张一鸣:10 年面试 2000 人,我发现混的好的人,全都有同一个特质
---------- END ----------欢迎关注我的公众号“程序员虎哥”,如果有收获,麻烦点一下“在看”~