0


万字详解 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 对象:

@DatapublicclassUser{privateString name;privateint age;privateString password;}
@DatapublicclassUserVo{privateString name;privateint age;}

常规的有两种方式:

  1. 使用 getter 和 setter 方法进行赋值,但是这个方法有着大量枯燥且重复的工作,一旦出错也不易于发现,可读性差。
  2. 使用 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)
  1. User 类
@Data@AutoMapper(target =UserVo.class)publicclassUser{privateString username;privateint age;privateString password;}
  1. 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)

有两种方式:

  1. 自定义一个类型转换器,通过 @AutoMapperuses 属性引入
  2. 通过 @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. 使用步骤

  1. 引入 hutool-core 依赖
  2. 在目标类上添加 @AutoMapMapper 注解
  3. 同时支持自定义类作为属性,需要在自定义类上增加 @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

,只要找到解决问题的合适方案就可以。

本文中若是有出现的错误请在评论区或者私信指出,我再进行改正优化。文章创作不易,如果对你有所帮助,请给博主一个宝贵的三连,感谢大家😘!!!



本文转载自: https://blog.csdn.net/weixin_54620350/article/details/140798567
版权归原作者 不爱生姜不吃醋 所有, 如有侵权,请联系我们删除。

“万字详解 MapStruct Plus,带你快速上手!”的评论:

还没有评论