前言在我们日常的开发程序中,为了层与层之间的解耦,我们在传输数据的时候一般会定义不同的对象来实现不同层之间的数据传输,比如xxxDTO、xxxVO、xxxQO在不同层之间,不可避免地经常需要将这些对象相互转换。今天给大家介绍一款对象转换工具MapStruct。代码简单安全,性能高。强烈推荐。MapStruct简介MapStruct是一个基于约定优于配置的代码生成器,极大地简化了JavaBean类型之间映射的实现。特点如下:基于注解。编译时自动生成映射转换代码。类型安全、高性能、无依赖性、易于理解和阅读。MapStruct入门1.这里引入依赖,使用Gradle构建;dependencies{implementation'org.mapstruct:mapstruct:1.4.2.Final'annotationProcessor'org.mapstruct:mapstruct-processor:1.4.2.Final'}2.需要转换的对象创建两个示例对象(例如转换Demo对象到DemoDto对象);/***源对象*/@DatapublicclassDemo{privateIntegerid;privateStringname;}/***targetobject*/@DatapublicclassDemoDto{privateIntegerid;privateStringname;}3、创建转换器只需要创建一个转换器接口类,并在类中添加@Mapper注解即可(官方示例推荐使用xxxMapper格式命名转换器名称);@MapperpublicinterfaceDemoMapper{//使用Mappers工厂获取DemoMapper实现类DemoMapperINSTANCE=Mappers.getMapper(DemoMapper.class);//定义接口方法,参数为源对象,返回值为目标对象DemoDtotoDemoDto(Demodemo);}4.验证publicstaticvoidmain(String[]args){Demodemo=newDemo();演示.setId(111);demo.setName("你好");DemoDtodemoDto=DemoMapper.INSTANCE.toDemoDto(demo);System.out.println("目标对象demoDto为:"+demoDto);//输出结果:目标对象demoDto为:DemoDto(id=111,name=hello)}测试结果如下:目标对象demoDto为:DemoDto(id=111,名字=你好);达到了我们预期的结果5.为什么自动生成的实现类可以声明一个接口来转换对象?我们看一下编译时MapStruct自动生成的实现类:@Generated(value="org.mapstruct.ap.MappingProcessor",date="2022-09-01T17:54:38+0800",comments="version:1.4.2.Final,编译器:来自gradle-language-java-7.3.jar的IncrementalProcessingEnvironment,环境:Java1.8.0_231(OracleCorporation)")publicclassDemoMapperImplimplementsDemoMapper{@OverridepublicDemoDtotoDemoDto(Demodemo){if(demo==null){返回null;}DemoDtodemoDto=newDemoDto();demoDto.setId(demo.getId());demoDto.setName(demo.getName());返回演示;我们自动生成了复杂的代码,实现类中使用了最基本的get和set方法,易读易懂,转换速度非常快。MapStructAdvanced上面的例子只是一个小测试,下面将展示MapStruct的强大。(限于篇幅,这里不再展示自动生成的实现类和验证结果,大家可以自行测试)。场景一:不同属性名,不同(基本)类型不同属性名:在方法中添加@Mapping注解来映射属性;不同基本类型的属性:基本类型和String等类型会自动转换;关键字:@Mapping注解。/***源对象*/@DatapublicclassDemo{privateIntegerid;privateStringname;}/***Targetobject*/@DatapublicclassDemoDto{privateStringid;privateStringfullname;}/***Converter*/@MapperpublicinterfaceDemoMapper{DemoMapperINSTANCE=Mappers.getMapper(DemoMapper.class);@Mapping(target="fullname",source="name")DemoDtotoDemoDto(Demodemo);}场景二:下面不同类型的统一映射例子中time1,time2,time3都会进行转换。详见如下注释:/***sourceobject*/@DatapublicclassDemo{privateIntegerid;私有字符串名称;/***time1和time2同名,time3转为time33*这里的time1、time2、time33都是Date类型*/privateDatetime1;私人约会时间2;privateDatetime3;}/***目标对象*/@DatapublicclassDemoDto{privateStringid;私有字符串名称;/***这里time1、time2、time33都是String类型*/privateStringtime1;私人字符串时间2;privateStringtime33;}/***Converter*/@MapperpublicinterfaceDemoMapper{DemoMapperINSTANCE=Mappers.GetMapper(DemoMapper.class);@Mapping(target="time33",source="time3")DemoDtotoDemoDto(Demodemo);//MapStruct会匹配所有://源类型是Date,目标类型是String属性,//按照下面的方法转换staticStringdate2String(Datedate){SimpleDateFormatsimpleDateFormat=newSimpleDateFormat("yyyy-MM-ddHH:mm:ss");StringstrDate=simpleDateFormat.format(date);返回海峡;}}场景三:固定值,忽略某个属性,时间到字符串格式一个例子演示了三种用法。具体说明看注释,通俗易懂:关键字:ignore,constant,dateFormat/***sourceobject*/@DatapublicclassDemo{privateIntegerid;私有字符串名称;privateDatetime;}/***targetobject*/@DatapublicclassDemoDto{privateStringid;私有字符串名称;privateStringtime;}/***conversionDemoMapper*/@MapperpublicinterfaceDemoMapper{DemoMapperINSTANCE=Mappers.getMapper(DemoMapper.class);//id属性不赋值@Mapping(target="id",ignore=true)//name属性固定赋值给"hello"@Mapping(target="name",constant="hello")//时间属性转换为yyyy-MM-ddHH:mm:ssformatstring@Mapping(target="time",dateFormat="yyyy-MM-ddHH:mm:ss")DemoDtotoDemoDto(Demodemo);}场景4:指定一个属性的转换方式场景2其中,我们统一按照一定的转换方式将一种类型转换为另一种类型;下面的例子是为某个属性指定一个方法:privateStringname;}/***targetobject*/@DatapublicclassDemoDto{privateStringid;privateStringname;}/***converter*/@MapperpublicinterfaceDemoMapper{DemoMapperINSTANCE=Mappers.getMapper(DemoMapper.class);//指定@Named为name属性的convertName方法进行转换@Mapping(target="name",qualifiedByName="convertName")DemoDtotoDemoDto(Demodemo);@Named("convertName")staticStringaaa(Stringname){return"Nameis:"+name;}}场景五:多个参数组合成一个对象如果有多个参数,@Mapping注解中的source必须指定是哪个参数,用点分隔:关键字:点(.)/***sourceobject*/@DatapublicclassDemo{privateIntegerid;privateStringname;}/***targetobject*/@DatapublicclassDemoDto{privateStringfullname;私人字符串时间stamp;}/***Converter*/@MapperpublicinterfaceDemoMapper{DemoMapperINSTANCE=Mappers.getMapper(DemoMapper.class);//fullname属性赋值给demo对象的name属性(这里注意.的用法)//timestamp属性赋值对于传入的时间参数@Mapping(target="fullname",source="demo.name")@Mapping(target="timestamp",source="time")DemoDtotoDemoDto(Demodemo,Stringtime);}场景六:如果目标对象已经存在,将源对象的属性覆盖到目标对象时,nullvalue一般不会被覆盖,所以需要在类的@Mapper注解上添加一个属性:nullValuePropertyMappingStrategy=NullValuePropertyMappingStrategy.IGNORE表示null值不赋值关键字:@MappingTarget注解,nullValuePropertyMappingStrategy/***sourceobject*/@DatapublicclassDemo{privateIntegerid;privateStringname;}/***targetobject*/@DatapublicclassDemoDto{privateStringid;私有字符串名称;}/***Converter*/@Mapper(unmappedTargetPolicy=ReportingPolicy.IGNORE,nullValuePropertyMappingStrategy=NullValuePropertyMappingStrategy.IGNORE)publicinterfaceDemoMapper{DemoMapperINSTANCE=Mappers.getMapper(DemoMapper.class);//将已有的目标对象作为参数传给DemoDtotoDemoDto(Demodemo,@MappingTargetDemoDtodto);}场景七:源对象的两个属性合并为一个属性。在这种情况下,可以使用@AfterMapping注解。关键词:@AfterMapping注解,@MappingTarget注解/***源对象*/@DatapublicclassDemo{privateIntegerid;私有字符串名字;privateStringlastName;}/***目标对象*/@DatapublicclassDemoDto{privateStringid;privateStringname;}/***Converter*/@MapperpublicinterfaceDemoMapper{DemoMapperINSTANCE=Mappers.getMapper(DemoMapper.class);DemoDtotoDemoDto(Demodemo);//转换完成后执行的方法,一般是源对象的两个属性合并为一个属性的场景//需要同时传入源对象和目标对象(@MappingTarget)作为参数,@AfterMappingstaticvoidafterToDemoDto(Demodemo,@MappingTargetDemoDtodemoDto){字符串名称=demo.getFirstName()+demo.getLastName();demoDto.setName(名称);}}小结本文介绍对象转换工具MapStruct库,以安全、简洁、优雅的方式优化我们的转换代码。从本文的示例场景可以看出,MapStruct提供了大量的功能和配置,可以让我们快速创建各种简单或复杂的映射器。
