背景在目前流行的系统设计中,对象模型一般分为多个层次,如VO、DTO、PO、BO等。这也产生了一个问题,经常需要在不同级别的模型之间进行转换。解决这个问题,目前经常使用三种方案:调用各个字段的getter/setter进行赋值。这个过程枯燥乏味,而且容易出错。同时极易造成代码行数迅速膨胀,可读性差。apache-commons、Spring等提供的BeanUtil工具类使用起来非常方便,一行代码即可实现映射。但其内部使用反射实现映射,性能较低,出现问题时难以调试。当需要个性化转换时,配置比较麻烦。不建议使用,尤其是对性能要求比较高的程序。mapstruct:该框架基于Java注解处理器,定义了一个转换接口。编译时会根据接口类和方法相关的注解自动生成转换实现类。生成的转换逻辑是基于getter/setter方法的,所以不会像BeanUtil等那样消耗它的性能。以上三种方法中,mapstruct是最好的。当然,美中不足的是,当系统比较复杂,对象多,结构复杂,或者有些项目设计会定义多层对象模型时(比如DDD领域设计),就需要定义更多转换接口和转换方法,这也是部分开发者放弃Mapstruct的主要原因。这里给大家介绍一个Mapstruct增强包——MapstructPlus,一个注解可以生成两个类之间的转换接口,让Java类型转换更加方便优雅,彻底摒弃BeanUtils。快速入门下面演示如何使用MapStructPlus映射两个对象。假设有两个类UserDto和User,分别代表数据层对象和业务层对象:UserDtopublicclassUserDto{privateStringusername;私人年龄;私人布尔年轻;//getter,setter,toString,equals,hashCode}UserpublicclassUser{privateStringusername;私人年龄;私人布尔年轻;//getter,setter,toString,equals,hashCode}添加依赖importmapstruct-plus-spring-boot-starterdependency:1.1.3io.github.linpeiliemapstruct-plus-spring-boot-starter<版本>${mapstruct-plus.version}org.apache.maven.pluginsmaven-compiler-plugin3.8.11.81.8io.github.linpeiliemapstruct-plus-processor${mapstruct-plus.version}指定对象映射关系在User或UserD上添加注解-@AutoMapper,并设置targetType为其他类例如:@AutoMapper(target=UserDto.class)publicclassUser{//...}测试@SpringBootTestpublicclassQuickStartTest{@AutowiredprivateConverterconverter;@Testpublicvoidtest(){Useruser=newUser();user.setUsername("jack");user.setAge(23);user.setYoung(false);UserDtouserDto=converter.convert(user,UserDto.class);System.out.println(userDto);//UserDto{username='jack',age=23,young=false}assertuser.getUsername().equals(userDto.getUsername());断言user.getAge()==userDto.getAge();断言user.isYoung()==userDto.isYoung();UsernewUser=converter.convert(userDto,User.class);System.out.println(newUser);//User{username='jack',age=23,young=false}assertuser.getUsername().equals(newUser.getUsername());断言user.getAge()==newUser.getAge();断言user.isYoung()==newUser.isYoung();}}小结引入依赖最后,MapstructPlus的使用步骤非常简单。在需要转换的类中添加AutoMapper注解,获取Converter实例,调用convert方法。这些功能与MapstructMapst完全兼容,以实现增强的操作。如果之前使用过Mapstruct,可以直接替换相关依赖。一个注解就可以实现两个对象之间的转换。比如在快速入门中,只在User类上添加注解@AutoMapper。MapstructPlus除了会生成User->UserDto的转换接口外,默认还会生成UserDto->User的转换接口。编译后可以查看生成的类,如下:自定义对象类型的属性自动转换。当需要转换的两个对象A和ADto,属性B和BDto为自定义类型,同时定义了@AutoMapper注解,在生成转换A和ADto时,会自动依赖属性B和B的转换接口BDto实现相应的属性转换。例如:有两组对象模型:汽车(Car)和座位配置(SeatConfiguration),其中Car依赖于SeatConfiguration。两组对象结构相同,但代表的层次不同。以下是dto层的对象定义示例:CarDto@AutoMapper(target=Car.class)}测试:@TestpublicvoidcarConvertTest(){CarDtocarDto=newCarDto();SeatConfigurationDtoseatConfigurationDto=newSeatConfigurationDto();seatConfigurationDto.setSeatCount(4);汽车car=converter.convert(carDto,Car.class);System.out.println(汽车);//Car(seatConfiguration=SeatConfiguration(seatCount=4))assertcar.getSeatConfiguration()!=null;assertcar.getSeatConfiguration().getSeatCount()==4;}单个对象转换多个对象。MapstructPlus提供@AutoMappers注解,支持配置多个目标对象进行转换。例如:有三个对象,User、UserDto、UserVO,User需要分别和另外两个对象进行转换。那么可以这样配置:@Data@AutoMappers({@AutoMapper(target=UserDto.class),@AutoMapper(target=UserVO.class)})publicclassUser{//...}MaptoobjectMapstructPlus提供@AutoMapMapper注解支持生成Map到当前类的接口中。同时也支持将地图中嵌套的Map转为自定义类嵌套自定义类的场景。其中,map中的value支持的类型如下:StringBigDecimalBigIntegerIntegerLongDoubleNumberBooleanDateLocalDateTimeLocalDateLocalTimeURIURLCalendarCurrency自定义类(自定义类还需要添加@AutoMapMapper注解例如:有如下两个接口:MapModelA@Data@AutoMapMapperpublicclassMapModelA{privateStringstr;privateinti1;privateLongl2;privateMapModelBmapModelB;}MapModelB@Data@AutoMapMapperpublicclassMapModelB{privateDatedate;}Test:@Testpublicvoidtest(){MapmapModel1=newHashMap<>();mapModel1.put("str","1jkf1ijkj3f");mapModel1.put("i1",111);mapModel1.put("l2",11231);MapmapModel2=newHashMap<>();mapModel2.put("日期",DateUtil.parse("2023-02-2301:03:23"));mapModel1.put("mapModelB",mapModel2);finalMapModelAmapModelA=converter.convert(mapModel1,MapModelA.class);System.out.println(mapModelA);//MapModelA(str=1jkf1ijkj3f,i1=111,l2=11231,mapModelB=MapModelB(date=2023-02-2301:03:23))}支持自定义转换自定义属性转换MapstructPlus提供@AutoMapping注解,注解后编译完成后,会变成Mapstruct中的@Mapping注解,实现了几个常用的注解属性指定不同名称的属性字段映射转换例如Car类中的属性wheels属性转换为CarDto类型时,需要将该字段映射到wheelList,可以在wheels上添加如下注解:@AutoMapper(target=CarDto.class)publicclassCar{@AutoMapping(target="wheelList")privateListwheels;}自定义时间格式Date类型属性转String类型时,可以使用dateFormat指定时间格式:@AutoMapper(target=Goods.class)publicclassGoodsDto{@AutoMapping(target="takeDownTime",dateFormat="yyyy-MM-ddHH:mm:ss")privateDatetakeDownTime;}数字类型时的自定义数字格式(double,long,BigDecimal)转String类型时,可以通过numberFormat指定java.text.DecimalFormat支持的格式:@AutoMapper(target=Goods.class)publicclassGoodsDto{@AutoMapping(target="price",numberFormat="$#.00")privateintprice;}自定义Java表达式@AutoMapping提供表达式属性,支持配置一段可执行的Java代码执行特定的转换逻辑。例如,当一个List属性想要转换为以,分隔的字符串时,可以通过这样的配置执行转换逻辑:@AutoMapper(target=UserDto.class)publicclassUser{@AutoMapping(target="educations",expression="java(java.lang.String.join(",",source.getEducationList()))")privateListeducationList;}@AutoMapping注解提供的自定义类型转换器uses属性,引入了一个自定义类型转换器,为当前类提供转换。示例场景:项目中会有以,分隔的字符串。在某些类中,需要根据逗号拆分成字符串集合。对于这种场景,有两种方式:一是可以指定字段映射的表达式,但是这种情况下需要为每个字段添加一个表达式,比较复杂且容易出错。其次,您可以自定义类型转换器并通过uses使用公共接口StringToListString。{默认ListstringToListString(Stringstr){returnStrUtil.split(str);}}@AutoMapper(target=User.class,uses=StringToListStringConverter.class)publicclassUserDto{privateStringusername;私人年龄;私人布尔年轻;@AutoMapping(target="educationList")私人字符串教育;//...}测试:@SpringBootTestpublicclassQuickStartTest{@AutowiredprivateConverter转换器;@TestpublicvoidueseTest(){UserDtouserDto=newUserDto();userDto.setEducations("1,2,3");最终用户user=converter.convert(userDto,User.class);System.out.println(user.getEducationList());//[1,2,3]assertuser.getEducationList().size()==3;}}更多特性请参考文末官方文档与Mapstruct相比,MapstructPlus继承了其高性能的特点,同时增强了其可移植性和快速开发的特点。在系统模型设计较好的情况下(属性和类型基本相同),开发成本极低。是时候和B谈谈了eanUtils说再见