与其明天开始,不如现在行动!
文章目录
前言
Mapstruct 是一个代码生成器,基于约定优于配置的方法,极大简化了 Java bean 类型之间映射的实现,特点:速度快、类型安全且易于理解。
Mapstruct Plus 是 MapStruct 的增强工具(类似于 Mybatis 和 Mybatis Plus 的关系),其在 MapStruct 的基础上,实现了自动生成 Mapper 接口的功能,并强化了部分功能,使 Java 类型转换更便捷、优雅。
MapStruct Plus 内嵌 MapStruct,和 MapStruct 完全兼容,如果之前已经使用 MapStruct,可以无缝替换依赖。
参考网站:
MapStruct 官网
MapStruct Plus 官网
一、为什么要用 MapStruct(背景)
目前的系统开发中,对象模型之间需要相互转换,比如一个 User 对象需要转换为 UserVo 对象:
@DatapublicclassUser{privateString name;privateint age;privateString password;}
@DatapublicclassUserVo{privateString name;privateint age;}
常规的有两种方式:
- 使用 getter 和 setter 方法进行赋值,但是这个方法有着大量枯燥且重复的工作,一旦出错也不易于发现,可读性差。
- 使用 spring 提供的
BeanUtils
工具类进行对象之间的转换,如下代码块所示,但是因为内部采用反射实现,性能低下,出现问题时不容易调试。
// 创建一个 User 对象User user =newUser();
user.setName("wen");
user.setAge(18);
user.setPassword("123456");// 创建一个 UserVo 对象UserVo userVo =newUserVo();// 一行代码实现 user => userVoBeanUtils.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)publicclassUser{privateString username;privateint age;privateString password;}
- UserVo 类
@Data@AutoMapper(target =UserVo.class)publicclassUser{privateString username;privateint age;privateString password;}
3. 编写测试代码
@SpringBootTestpublicclassQuickStartTest{@AutowiredprivateConverter converter;@Testpublicvoidtest(){// 创建 User 对象User user =newUser();
user.setUsername("wen");
user.setAge(18);
user.setPassword("123456");// 使用 MapStruct plus 进行对象间转换:User =》 UserVoUserVo 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 实现的:
publicUserVoconvert(User arg0){if( arg0 ==null){returnnull;}UserVo userVo =newUserVo();
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 的转换器:
@ComponentpublicclassStringToListConverter{publicList<String>stringToList(String str){if(str ==null){returnCollections.emptyList();}returnArrays.asList(str.split(","));}}
List 转为 String 的转换器:
@ComponentpublicclassListToStringConverter{publicStringlistToString(List<String> list){if(list ==null|| list.isEmpty()){returnnull;}returnString.join(",", list);}}
2. 使用类型转换器
第二步,使用该类型转换器,即在
@AutoMapper
注解中使用 uses,且给需要转化的属性加上
@AutoMapping
注解,target 指向另一个需要转化的属性。
User 类:
@Data@AutoMapper(target =UserVo.class,uses=StringToListConverter.class)publicclassUser{privateString name;privateint age;privateString password;@AutoMapping(target ="tagList")privateString tags;}
UserVo 类:
@Data@AutoMapper(target =User.class,uses=ListToStringConverter.class)publicclassUserVo{privateString name;privateint age;@AutoMapping(target ="tags")privateList<String> tagList;}
3. 进行测试
第三步,进行测试。
@SpringBootTestpublicclassQuickStartTest{@AutowiredprivateConverter converter;@Testpublicvoidtest(){// 创建一个 User 对象User user =newUser();
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@AutoMapMapperpublicclassBody{privateint height;privateint weight;}
Person 类:
@Data@AutoMapMapperpublicclassPerson{privateString name;privateInteger age;privateBody body;}
3. 转换测试
@SpringBootTestpublicclassMapToObjectTest{@AutowiredprivateConverter converter;@Testpublicvoidtest(){// 创建一个 Map,键是 Body 的属性名,值是属性值Map<String,Object> map1 =newHashMap<>();
map1.put("height",180);
map1.put("weight",150);// 创建第二个 Map,键是 Person 的属性名,值是属性值Map<String,Object> map2 =newHashMap<>();
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")publicenumStateEnum{ENABLE(1,"启用"),DISABLE(0,"禁用");privatefinalint code;privatefinalString desc;}
2. 定义要转换的对象
定义一个保存枚举类的类 Course,再定义一个需要转换的 CourseVo 类:
Course 类:
@Data@AutoMapper(target =CourseVo.class)publicclassCourse{privateStateEnum state;}
CourseVo 类:
@DatapublicclassCourseVo{privateInteger state;}
3. 转换测试
@SpringBootTestpublicclassEnumToValueTest{@AutowiredprivateConverter converter;@Testpublicvoidtest(){// 创建一个 Course 对象Course course =newCourse();
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)publicclassUser{privateString name;privateint age;privateString password;@AutoMapping(target ="tagList")privateString tags;}
Course 类:
@Data@AutoMapper(target =UserVo.class)publicclassCourse{@AutoMapping(targetClass =UserVo.class, ignore =true)// 忽略 UserVo 中的 name 属性privateString name;privateString teacher;}
UserVo 类:
@Data@AutoMappers({@AutoMapper(target =User.class,uses=ListToStringConverter.class),@AutoMapper(target =Course.class)})publicclassUserVo{@AutoMappings({@AutoMapping(targetClass =User.class),@AutoMapping(targetClass =Course.class, ignore =true)})privateString name;privateint age;@AutoMapping(targetClass =User.class, target ="tags")privateList<String> tagList;privateString teacher;}
2. 转换测试
@SpringBootTestpublicclassOneToOthersTest{@AutowiredprivateConverter converter;@Testpublicvoidtest(){// 创建 User 对象User user =newUser();
user.setName("wen");
user.setAge(18);
user.setPassword("123456");
user.setTags("Java,Python,Go,C++");// 创建 Course 对象Course course =newCourse();
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
,只要找到解决问题的合适方案就可以。
本文中若是有出现的错误请在评论区或者私信指出,我再进行改正优化。文章创作不易,如果对你有所帮助,请给博主一个宝贵的三连,感谢大家😘!!!
版权归原作者 不爱生姜不吃醋 所有, 如有侵权,请联系我们删除。