万字详解 MapStruct Plus,带你快速上手!
MapStruct Plus 是 MapStruct 的增强工具(类似于 Mybatis 和 Mybatis Plus 的关系),其在 MapStruct 的基础上,实现了自动生成 Mapper 接口的功能,并强化了部分功能,使 Java 类型转换更便捷、优雅。
与其明天开始,不如现在行动!
文章目录
前言
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
,只要找到解决问题的合适方案就可以。
本文中若是有出现的错误请在评论区或者私信指出,我再进行改正优化。文章创作不易,如果对你有所帮助,请给博主一个宝贵的三连,感谢大家😘!!!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)