背景
在当初风行的零碎设计中,个别会将对象模型划分为多个档次,例如 VO、DTO、PO、BO 等等。这同时也产生了一个问题,常常须要进行不同层级的模型之间互相转换。
针对这种问题,目前常会采纳三种计划:
- 调用每个字段的 getter/setter 进行赋值。这个过程,干燥且乏味,容易出错的同时,极易容易造成代码行数迅速收缩,可浏览性差。
- apache-commons、Spring 等提供的
BeanUtil
工具类,这种工具类应用十分不便,一行代码即可实现映射。但其外部采纳反射的形式来实现映射,性能低下,呈现问题时,调试艰难,当须要个性化转换时,配置麻烦,十分不倡议应用,特地是对于性能要求比拟高的程序中。 - mapstruct:这个框架是基于 Java 正文处理器,定义一个转换接口,在编译的时候,会依据接口类和办法相干的注解,主动生成转换实现类。生成的转换逻辑,是基于 getter/setter 办法的,所以不会像
BeanUtil
等耗费其性能。
下面的三种办法中,最优良的莫属 mapstruct 了,当然,美中不足的就是,当零碎较为简单,对象较多且结构复杂,又或者有的我的项目设计中会定义多层对象模型(如 DDD 畛域设计),须要定义较多的转换接口和转换方法,这也是一些开发者放弃 Mapstruct 的次要起因。
这里,就要给大家介绍一个 Mapstruct 的加强包 —— Mapstruct Plus,一个注解,能够生成两个类之间的转换接口,使 Java 类型转换更加便捷、优雅,彻底摈弃 BeanUtils。
疾速开始
上面演示如何应用 MapStruct Plus 来映射两个对象。
假如有两个类 UserDto
和 User
,别离示意数据层对象和业务层对象:
UserDto
public class UserDto { private String username; private int age; private boolean young; // getter、setter、toString、equals、hashCode}
User
public class User { private String username; private int age; private boolean young; // getter、setter、toString、equals、hashCode}
增加依赖
引入 mapstruct-plus-spring-boot-starter
依赖:
<properties> <mapstruct-plus.version>1.1.3</mapstruct-plus.version></properties><dependencies> <dependency> <groupId>io.github.linpeilie</groupId> <artifactId>mapstruct-plus-spring-boot-starter</artifactId> <version>${mapstruct-plus.version}</version> </dependency></dependencies><build> <plugins> <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>io.github.linpeilie</groupId> <artifactId>mapstruct-plus-processor</artifactId> <version>${mapstruct-plus.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins></build>
指定对象映射关系
在 User
或者 UserDto
下面减少注解 —— @AutoMapper
,并设置 targetType
为对方类。
例如:
@AutoMapper(target = UserDto.class)public class User { // ...}
测试
@SpringBootTestpublic class QuickStartTest { @Autowired private Converter converter; @Test public void test() { User user = new User(); user.setUsername("jack"); user.setAge(23); user.setYoung(false); UserDto userDto = converter.convert(user, UserDto.class); System.out.println(userDto); // UserDto{username='jack', age=23, young=false} assert user.getUsername().equals(userDto.getUsername()); assert user.getAge() == userDto.getAge(); assert user.isYoung() == userDto.isYoung(); User newUser = converter.convert(userDto, User.class); System.out.println(newUser); // User{username='jack', age=23, young=false} assert user.getUsername().equals(newUser.getUsername()); assert user.getAge() == newUser.getAge(); assert user.isYoung() == newUser.isYoung(); }}
小结
引入依赖后,应用 Mapstruct Plus 步骤非常简单。
- 给须要转换的类增加
AutoMapper
注解 - 获取
Converter
实例,调用convert
办法即可
个性
齐全兼容 Mapstruct
Mapst实现了加强操作,如果之前曾经应用了 Mapstruct,能够间接替换相干依赖。
单注解即可实现两个对象互相转换
例如疾速开始中,只在 User
类下面减少注解 @AutoMapper
,Mapstruct Plus 除了会生成 User
-> UserDto
的转换接口,默认还会生成 UserDto
-> User
的转换接口。
编译后,能够查看生成的类,如下:
自定义对象类型的属性主动转换
当两个须要转换的对象 A
和 ADto
,其中的属性 B
和 BDto
是自定义类型,并且也定义了 @AutoMapper
注解的话,在生成转换 A
和 ADto
时,会主动依赖属性 B
和 BDto
的转换接口,实现其相应的属性转换。
例如:
别离有两组对象模型:汽车(Car) 和座椅配置(SeatConfiguration),其中 Car 依赖 SeatConfiguration。
两组对象构造统一,只不过代表层级不一样,这里拿 dto 层的对象定义举例:
CarDto
@AutoMapper(target = Car.class)public class CarDto { private SeatConfigurationDto seatConfiguration;}
SeatConfiguration
@AutoMapper(target = SeatConfiguration.class)public class SeatConfigurationDto { private int seatCount;}
测试:
@Testpublic void carConvertTest() { CarDto carDto = new CarDto(); SeatConfigurationDto seatConfigurationDto = new SeatConfigurationDto(); seatConfigurationDto.setSeatCount(4); carDto.setSeatConfiguration(seatConfigurationDto); final Car car = converter.convert(carDto, Car.class); System.out.println(car); // Car(seatConfiguration=SeatConfiguration(seatCount=4)) assert car.getSeatConfiguration() != null; assert car.getSeatConfiguration().getSeatCount() == 4;}
单个对象对多个对象进行转换
Mapstruct Plus 提供了 @AutoMappers
注解,反对配置多个指标对象,进行转换。
例如:有三个对象,User
、UserDto
、UserVO
,User
别离要和其余两个对象进行转换。则能够按如下配置:
@Data@AutoMappers({ @AutoMapper(target = UserDto.class), @AutoMapper(target = UserVO.class)})public class User { // ...}
Map 转对象
Mapstruct Plus 提供了 @AutoMapMapper
注解,反对生成 Map<String, Object>
转换为以后类的接口。同时,还反对 map 中嵌套 Map<String, Object>
转换为自定义类嵌套自定义类的场景。
其中,map 中的 value 反对的类型如下:
- String
- BigDecimal
- BigInteger
- Integer
- Long
- Double
- Number
- Boolean
- Date
- LocalDateTime
- LocalDate
- LocalTime
- URI
- URL
- Calendar
- Currency
- 自定义类(自定义类也须要减少
@AutoMapMapper
注解
例如:
有如下两个接口:
MapModelA
@Data@AutoMapMapperpublic class MapModelA { private String str; private int i1; private Long l2; private MapModelB mapModelB;}
MapModelB
@Data@AutoMapMapperpublic class MapModelB { private Date date; }
测试:
@Testpublic void test() { Map<String, Object> mapModel1 = new HashMap<>(); mapModel1.put("str", "1jkf1ijkj3f"); mapModel1.put("i1", 111); mapModel1.put("l2", 11231); Map<String, Object> mapModel2 = new HashMap<>(); mapModel2.put("date", DateUtil.parse("2023-02-23 01:03:23")); mapModel1.put("mapModelB", mapModel2); final MapModelA mapModelA = converter.convert(mapModel1, MapModelA.class); System.out.println(mapModelA); // MapModelA(str=1jkf1ijkj3f, i1=111, l2=11231, mapModelB=MapModelB(date=2023-02-23 01:03:23))}
反对自定义转换
自定义属性转换
Mapstruct Plus 提供了 @AutoMapping
注解,该注解在编译后,会变为 Mapstruct 中的 @Mapping
注解,曾经实现了几个罕用的注解属性。
指定不同名称属性字段映射转换
例如,Car
类中属性 wheels
属性,转换 CarDto
类型时,须要将该字段映射到 wheelList
下面,能够在 wheels
下面减少如下注解:
@AutoMapper(target = CarDto.class)public class Car { @AutoMapping(target = "wheelList") private List<String> wheels;}
自定义工夫格式化
在将 Date
类型的属性,转换为 String
类型时,能够通过 dateFormat
来指定工夫格式化:
@AutoMapper(target = Goods.class)public class GoodsDto { @AutoMapping(target = "takeDownTime", dateFormat = "yyyy-MM-dd HH:mm:ss") private Date takeDownTime;}
自定义数字格式化
当数字类型(double
、long
、BigDecimal
)转换为 String
类型时,能够通过 numberFormat
指定 java.text.DecimalFormat
所反对的格局:
@AutoMapper(target = Goods.class)public class GoodsDto { @AutoMapping(target = "price", numberFormat = "$#.00") private int price;}
自定义 Java 表达式
@AutoMapping
提供了 expression
属性,反对配置一段可执行的 Java 代码,来执行具体的转换逻辑。
例如,当一个 List<String>
的属性,想要转换为用 ,
分隔的字符串时,能够通过该配置,来执行转换逻辑:
@AutoMapper(target = UserDto.class)public class User { @AutoMapping(target = "educations", expression = "java(java.lang.String.join(",", source.getEducationList()))") private List<String> educationList;}
自定义类型转换器
@AutoMapping
注解提供了 uses
属性,引入自定义的类型转换器,来提供给以后类转换时应用。
实例场景:
我的项目中会有字符串用 ,
分隔,在一些类中,须要依据逗号拆分为字符串汇合。针对于这种场景,能够有两种形式:首先能够指定字段映射时的表达式,但须要对每种该状况的字段,都增加表达式,简单且容易出错。
第二,就能够自定义一个类型转换器,通过 uses
来应用
public interface StringToListString { default List<String> stringToListString(String str) { return StrUtil.split(str); }}@AutoMapper(target = User.class, uses = StringToListStringConverter.class)public class UserDto { private String username; private int age; private boolean young; @AutoMapping(target = "educationList") private String educations; // ......}
测试:
@SpringBootTestpublic class QuickStartTest { @Autowired private Converter converter; @Test public void ueseTest() { UserDto userDto = new UserDto(); userDto.setEducations("1,2,3"); final User user = converter.convert(userDto, User.class); System.out.println(user.getEducationList()); // [1, 2, 3] assert user.getEducationList().size() == 3; }}
更多
更多个性,能够查看官网文档
结束语
Mapstruct Plus 绝对于 Mapstruct 来说,继承了其高性能的特点,同时加强了其便携性和疾速开发的个性,在零碎模型设计较好(属性及类型基本一致)的状况下,开发成本极低,是时候和 BeanUtils
说再见了。