MapStruct Plus的使用教程
作者:使用
前言
Mapstruct 是一个代码生成器,基于约定优于配置的方法,极大简化了 Java bean 类型之间映射的实现,特点:速度快、类型安全且易于理解。
Mapstruct Plus 是 MapStruct 的增强工具(类似于 Mybatis 和 Mybatis Plus 的关系),其在 MapStruct 的基础上,实现了自动生成 Mapper 接口的功能,并强化了部分功能,使 Java 类型转换更便捷、优雅。
MapStruct Plus 内嵌 MapStruct,和 MapStruct 完全兼容,如果之前已经使用 MapStruct,可以无缝替换依赖。
参考网站:
MapStruct 官网
MapStruct Plus 官网
一、为什么要用 MapStruct(背景)
目前的系统开发中,对象模型之间需要相互转换,比如一个 User 对象需要转换为 UserVo 对象:
@Data public class User { private String name; private int age; private String password; }
@Data public class UserVo { private String name; private int age; }
常规的有两种方式:
- 使用 getter 和 setter 方法进行赋值,但是这个方法有着大量枯燥且重复的工作,一旦出错也不易于发现,可读性差。
- 使用 spring 提供的
BeanUtils
工具类进行对象之间的转换,如下代码块所示,但是因为内部采用反射实现,性能低下,出现问题时不容易调试。
// 创建一个 User 对象 User user = new User(); user.setName("wen"); user.setAge(18); user.setPassword("123456"); // 创建一个 UserVo 对象 UserVo userVo = new UserVo(); // 一行代码实现 user => userVo BeanUtils.copyProperties(user, userVo);
所以 MapStruct 应运而生,这个框架是基于 Java 注释处理器,定义一个转换接口,在编译的时候会根据接口类和方法相关的注解,自动生成实现类,底层是基于 getter 和 setter 方法的,比 BeanUtils
的性能要高。然而美中不足的是,当需要转换的对象较多或者结构复杂的时候,需要定义较多的转换接口和转换方法。
此时,就可以使用 MapStruct Plus ,一个注解就可以生成两个类之间的转换接口,使 Java 类型转换更加便捷和优雅。
二、MapStruct Plus 的快速开始
本文以 Spring Boot 项目为例,版本:
Spring Boot:3.3.2
JDK:17
Lombok:1.18.34
1. 引入依赖
引入 mapstruct-plus-spring-boot-starter
依赖
<dependency> <groupId>io.github.linpeilie</groupId> <artifactId>mapstruct-plus-spring-boot-starter</artifactId> <version>1.4.3</version> </dependency>
引入 Maven 插件,配置项目的构建过程(这一步非常非常重要!!!)引入 Maven 插件,配置项目的构建过程(这一步非常非常重要!!!)引入 Maven 插件,配置项目的构建过程(这一步非常非常重要!!!)
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>17</source> <target>17</target> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> <path> <groupId>io.github.linpeilie</groupId> <artifactId>mapstruct-plus-processor</artifactId> <version>${mapstruct-plus.version}</version> </path> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>0.2.0</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
最新版本依赖可以查看:MapStruct Plus 的 Maven 仓库地址
2. 指定对象映射关系
在 User 或者 UserVo 上面增加注解 —— @AutoMapper,并设置 target 为对方类。
以下面代码举例,添加注解:@AutoMapper(target = UserVo.class)
- User 类
@Data @AutoMapper(target = UserVo.class) public class User { private String username; private int age; private String password; }
- UserVo 类
@Data @AutoMapper(target = UserVo.class) public class User { private String username; private int age; private String password; }
3. 编写测试代码
@SpringBootTest public class QuickStartTest { @Autowired private Converter converter; @Test public void test() { // 创建 User 对象 User user = new User(); user.setUsername("wen"); user.setAge(18); user.setPassword("123456"); // 使用 MapStruct plus 进行对象间转换:User =》 UserVo UserVo userVo = converter.convert(user, UserVo.class); // 输出转换之后的对象 System.out.println(userVo); // 断言测试 assert user.getUsername().equals(userVo.getUsername()); assert user.getAge() == userVo.getAge(); } }
4. 运行结果
测试通过,输出:
5. 原理解析
通过以上示例可以看出,User 对象转化为 UserVo 对象主要是UserVo userVo = converter.convert(user, UserVo.class);
这行代码,其底层也很简单,原理是通过 getter 和 setter 实现的:
public UserVo convert(User arg0) { if ( arg0 == null ) { return null; } UserVo userVo = new UserVo(); userVo.setUsername( arg0.getUsername() ); userVo.setAge( arg0.getAge() ); return userVo; }
该代码被保存在 target 包中,具体路径:target/generated-sources/annotations/实体类存放路径
通过上图,可以看到,哪怕没有给 UserVo 实体类使用@AutoMapper
注解,MapStruct Plus 会自动生成 User 转 UserVo 的接口和实现类,同时也会生成 UserVo 转换为 User 的实体类和接口。
以上为重要规则,下面也能用得到!!!
三、自定义实体类中的属性转换
在上面的例子中,两个实体类中对应的属性都是同一种类型,那么想要自定义属性比如:后端存储的是字符串 String 类型的属性,想给前端返回一个 List 类型的属性,可以根据规则进行转换。
下面的举例是 String 属性和 List 属性之间的相互转化(String 《===》List)
有两种方式:
- 自定义一个类型转换器,通过
@AutoMapper
的uses
属性引入 - 通过
@AutoMapping
中配置的expression
表达式配置
1. 自定义一个类型转换器
首先定义两个类型转换器,一个是 String 转为 List,一个是 List 是 String。且两个类型转换器需要定义为 Spring 的 Bean,即使用 @Component
注解。
String 转为 List 的转换器:
@Component public class StringToListConverter { public List<String> stringToList(String str) { if (str == null) { return Collections.emptyList(); } return Arrays.asList(str.split(",")); } }
List 转为 String 的转换器:
@Component public class ListToStringConverter { public String listToString(List<String> list) { if (list == null || list.isEmpty()) { return null; } return String.join(",", list); } }
2. 使用类型转换器
第二步,使用该类型转换器,即在 @AutoMapper
注解中使用 uses,且给需要转化的属性加上 @AutoMapping
注解,target 指向另一个需要转化的属性。
User 类:
@Data @AutoMapper(target = UserVo.class, uses = StringToListConverter.class) public class User { private String name; private int age; private String password; @AutoMapping(target = "tagList") private String tags; }
UserVo 类:
@Data @AutoMapper(target = User.class, uses = ListToStringConverter.class) public class UserVo { private String name; private int age; @AutoMapping(target = "tags") private List<String> tagList; }
3. 进行测试
第三步,进行测试。
@SpringBootTest public class QuickStartTest { @Autowired private Converter converter; @Test public void test() { // 创建一个 User 对象 User user = new User(); user.setName("wen"); user.setAge(18); user.setPassword("123456"); user.setTags("Java,Python,C++"); // 转换 UserVo userVo = converter.convert(user, UserVo.class); System.out.println(userVo); assert userVo.getTagList().size() == 3; } }
测试结果:
测试用例通过,User 类中的 String 类型的 tags 属性,成功转化为 UserVo 类中的 List 类型的 tagList 属性。
还有一种方法是直接在注解中写表达式,但是博主觉得这种方式没有自定义转换器好,所以在本文中不列举
如果感兴趣,详情请参考:表达式自定义属性转换
四、Map 转为 Object
MapStruct Plus 提供了 Map<String, Object>
转化为对象的功能。
转换逻辑:针对目标类中的一个属性,首先会判断 Map 中是否存在该键,如果存在的话,首先判断类型,
- 如果类型相同,直接强转
- 若果类型不同,会使用 Hutool 提供的类型转换工具尝试转换为目标类型
MapStruct Plus 在 1.4.0+ 版本取消了内置 Hutool 框架,如果需要用到 Map 转化为对象的功能时,需要引入 hutool-core
这个依赖,最新版本查看:Hutool 依赖库
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-core</artifactId> <version>5.8.29</version> </dependency>
1. 使用步骤
- 引入
hutool-core
依赖 - 在目标类上添加
@AutoMapMapper
注解 - 同时支持自定义类作为属性,需要在自定义类上增加
@AutoMapMapper
注解
2. 定义对象
为了更好的理解,直接用最复杂的 Map 转对象的例子举例,即内部属性既有基本类型,也有自定义的对象
定义一个 Body 类,里面有身高体重属性,定义一个 Person 类,里面有基本信息和一个 Body 类型的属性。
Body 类:
@Data @AutoMapMapper public class Body { private int height; private int weight; }
Person 类:
@Data @AutoMapMapper public class Person { private String name; private Integer age; private Body body; }
3. 转换测试
@SpringBootTest public class MapToObjectTest { @Autowired private Converter converter; @Test public void test() { // 创建一个 Map,键是 Body 的属性名,值是属性值 Map<String, Object> map1 = new HashMap<>(); map1.put("height", 180); map1.put("weight", 150); // 创建第二个 Map,键是 Person 的属性名,值是属性值 Map<String, Object> map2 = new HashMap<>(); map2.put("name", "wen"); map2.put("age", 18); map2.put("body", map1); // 将 Map2 转化为 Person 对象 Person person = converter.convert(map2, Person.class); System.out.println(person); } }
测试成功,Map 对象成功转化为 Person 对象:
五、枚举类型转换
枚举类型的转换,需要在枚举类上添加 @AutoEnumMapper
注解,增加该注解后,在任意类型中需要转换该枚举时都可以自动转换。
使用 @AutoEnumMapper
注解的时候,需要注意:这个枚举类必须要有一个可以保证唯一的字段,并将该字段添加到注解的 value
属性中
1. 定义一个枚举类
定义一个状态枚举类,唯一字段是 code,用来表示开始还是关闭:
@Getter @AllArgsConstructor @AutoEnumMapper("code") public enum StateEnum { ENABLE(1, "启用"), DISABLE(0, "禁用"); private final int code; private final String desc; }
2. 定义要转换的对象
定义一个保存枚举类的类 Course,再定义一个需要转换的 CourseVo 类:
Course 类:
@Data @AutoMapper(target = CourseVo.class) public class Course { private StateEnum state; }
CourseVo 类:
@Data public class CourseVo { private Integer state; }
3. 转换测试
@SpringBootTest public class EnumToValueTest { @Autowired private Converter converter; @Test public void test() { // 创建一个 Course 对象 Course course = new Course(); course.setState(StateEnum.ENABLE); // 将 Course 对象转换为 CourseVo 对象 CourseVo courseVo = converter.convert(course, CourseVo.class); System.out.println(courseVo); // 将 CourseVo 对象转换为 Course 对象 Course course1 = converter.convert(courseVo, Course.class); System.out.println(course1); } }
测试成功,Enum 可以转化为整形,整形也可以转化为 Enum:
4. 注意
枚举和使用枚举的类需要在同一个模块(module)中。
当枚举与要使用的类型,不在同一个模块中,是不能自动转换的,需要指定依赖关系。在 @AutoMapper
注解中,可以通过 useEnums
来指定需要依赖的枚举类列表。
六、一个类与多个类之间的转换
MapStruct Plus 还支持一个类和多个类进行转换,可以通过 @AutoMappers
来配置,该注解支持配置多个 @AutoMapper
。
在配置多个类进行转化的时候,多个类可能有相同的属性,为了解决属性冲突的问题,可以使用 @AutoMappings
指定多个转换规则,并且在使用 @AutoMapping
注解时,配置 targetClass
属性,指定当前规则的目标转化类。
如果配置 @AutoMapping
注解时,没有指定 targetClass
,那么当前规则就会应用所有类转换。
1. 定义对象
定义一个 User
类,一个 Course
类,一个 UserVo
类。其中 UserVo
类将与 User
类和 Course
类互相映射(UserVo 《===》User、Course
)。User
类和 Course
类都有 name
属性,但是只将 User
类中的 name
属性映射。
User 类:
@Data @AutoMapper(target = UserVo.class, uses = StringToListConverter.class) public class User { private String name; private int age; private String password; @AutoMapping(target = "tagList") private String tags; }
Course 类:
@Data @AutoMapper(target = UserVo.class) public class Course { @AutoMapping(targetClass = UserVo.class, ignore = true) // 忽略 UserVo 中的 name 属性 private String name; private String teacher; }
UserVo 类:
@Data @AutoMappers({ @AutoMapper(target = User.class, uses = ListToStringConverter.class), @AutoMapper(target = Course.class) }) public class UserVo { @AutoMappings({ @AutoMapping(targetClass = User.class), @AutoMapping(targetClass = Course.class, ignore = true) }) private String name; private int age; @AutoMapping(targetClass = User.class, target = "tags") private List<String> tagList; private String teacher; }
2. 转换测试
@SpringBootTest public class OneToOthersTest { @Autowired private Converter converter; @Test public void test() { // 创建 User 对象 User user = new User(); user.setName("wen"); user.setAge(18); user.setPassword("123456"); user.setTags("Java,Python,Go,C++"); // 创建 Course 对象 Course course = new Course(); course.setName("Java 开发"); course.setTeacher("教 Java 的老师"); // 转换(User 对象和 Course 对象)为 UserVo 对象 UserVo userVo = converter.convert(user, UserVo.class); userVo = converter.convert(course, userVo); System.out.println(userVo); // 转换 UserVo 对象为(User 对象和 Course 对象) user = converter.convert(userVo, User.class); course = converter.convert(userVo, Course.class); System.out.println(user); System.out.println(course); } }
3. 测试结果
总结
本文使用大量示例详细解释了在 Spring Boot
项目开发中使用 MapStruct Plus
的方法,多加练习熟能生巧。技术没有高低之分,不管是使用原始的 getter/setter
方法,还是使用 BeanUtils
,亦或者使用本文所介绍的 MapStruct Plus
,只要找到解决问题的合适方案就可以。
到此这篇关于MapStruct Plus的使用教程的文章就介绍到这了,更多相关MapStruct Plus内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!