背景
在写代码的时候,不同的层、不同的接口等地方,有时候都要对对象做一下转换,之前一直用的是BeanUtils,感觉用起来没那么灵活。后来看了一篇文章介绍了一个新的bean转换工具——mapstruct(https://mp.weixin.qq.com/s/jwT2wi6ZQL7VqMFTk6rEvg),考虑到旧的项目改起来比较麻烦,所以到最近接了个需求要开发新的项目,所以尝试一下在这个项目里面用这个工具。
代码案例
1.配置pom.xml
<org.mapstruct.version>1.4.1.Final</org.mapstruct.version>
...
需要lambok
...
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
...
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
2.创建一个mapper接口类:
//加上这个componentModel方便在spring项目里面直接注入
@Mapper(componentModel = "spring")
public interface XXXConverter{
}
3.针对不同情况的转换,创建不同的方法
简单粗暴映射两个对象的属性名相同的值(支持集合类,如list,set等)
XXXDto entityToDto(XXXEntity entity);
List<XXXDto> entityToDto(List<XXXEntity> entity)
有时候同一个对象返回给不同接口时,字段不同。按传统的方法,可能是创建不同的Vo类来映射,用MapStruct的话,可以分别创建不同的方法来映射给同一个对象
//通过ignore,可以忽略掉前端不需要的属性
@Mapping(target = "createdBy", ignore = true)
@Mapping(target = "createdGmt", ignore = true)
@Mapping(target = "modifiedBy", ignore = true)
@Mapping(target = "modifiedGmt", ignore = true)
XXXVo entityToVo1(XXXEntity entity);
@Mapping(target = "content", ignore = true)
@Mapping(target = "desc", ignore = true)
XXXVo entityToVo2(XXXEntity entity);
通过表达式(expression = "java(Java代码)")来进行来进行一些特殊的赋值:
@Mapping(target = "createdGmt", expression = "java(new java.util.Date())")
@Mapping(target = "modifiedGmt", expression = "java(new java.util.Date())")
XXXENtity reqToEntity(XXXReq req);
再进行列表映射的时候,被映射的对象有些属性再源对象里面没有,需要通过其他方法来写入的时候,可以通过@Context注解来实现:
@Mapping(target = "versionNo", expression = "java(version)")
XXXRecord entityToRecord(XXXEntitydiseaseAndSkill, @Context String versionNo);
//这里的方法名,要跟上面的相同
List<XXXRecord> entityToRecord(List<XXXEntity> entities, @Context String versionNo);
字段名相同,但是字段类型不同:
//源字段是List类型,目标字段是用竖线分隔的字符串
@Mapping(target = "imgUrls", expression = "java(transListToString(req.getImgUrls()))")
XXXENtity reqToEntity(XXXReq req);
default String transListToString(List<String> list) {
return String.join("|", list);
}
其他支持但还没用到的场景:日期格式化、多个对象映射到一个对象、map映射等等。想用到的但是还没找到办法实现的场景:只映射指定字段,其余全部忽略(目前仅支持忽略指定字段,其余默认映射)
4.在对应的业务类里面调用
XXXDto dto = xxxConverter.dtoToEntity(entity);
原理
通过lombok生成每个接口对应的代码,实际上生成的代码如下:
public XXXENtity reqToEntity(XXXReq req) {
if ( req == null ) {
return null;
}
XXXENtity entity= new XXXENtity ();
entity.setCreatedBy( req.getCreatedBy() );
entity.setCreatedId( req.getCreatedId() );
entity.setKnowledgeId( req.getKnowledgeId() );
entity.setDetail( req.getDetail() );
entity.setImgUrls( transListToString(req.getImgUrls()) );
entity.setCreatedGmt( new java.util.Date() );
entity.setModifiedGmt( new java.util.Date() );
return diseaseAndSkillFeedback;
}
优点
扩展性强,支持各种骚操作
根据 这篇文章 的性能压测来看,JMapper 和 MapStruct 的性能最好
因为MapStruct是生成静态代码,而BeanUtil是利用了反射,所以性能肯定比BeanUtil要好的
面对不同的字段需要,有时候可以用创建不同的映射方法来代替创建那么多同一层级不同的对象
如果字段修改了,可以在idea上安装MapStruct相关插件,来提醒(标黄):
场景越复杂,对比BeanUtil等工具而言,代码越简洁
缺点
每次改了对应的对象或mapper类,都需要clean之后再install
如果只是最基础的同字段名映射,其实没有直接用BeanUtil方便,毕竟用mapstruct的话,还得多建一个类
总结
越复杂的场景,用MapStruct越好;只是简单的场景的话,用不用都行。