MapStruct Plus的使用教程
前言
Mapstruct 是一個(gè)代碼生成器,基于約定優(yōu)于配置的方法,極大簡(jiǎn)化了 Java bean 類型之間映射的實(shí)現(xiàn),特點(diǎn):速度快、類型安全且易于理解。
Mapstruct Plus 是 MapStruct 的增強(qiáng)工具(類似于 Mybatis 和 Mybatis Plus 的關(guān)系),其在 MapStruct 的基礎(chǔ)上,實(shí)現(xiàn)了自動(dòng)生成 Mapper 接口的功能,并強(qiáng)化了部分功能,使 Java 類型轉(zhuǎn)換更便捷、優(yōu)雅。
MapStruct Plus 內(nèi)嵌 MapStruct,和 MapStruct 完全兼容,如果之前已經(jīng)使用 MapStruct,可以無(wú)縫替換依賴。
參考網(wǎng)站:
MapStruct 官網(wǎng)
MapStruct Plus 官網(wǎng)
一、為什么要用 MapStruct(背景)
目前的系統(tǒng)開發(fā)中,對(duì)象模型之間需要相互轉(zhuǎn)換,比如一個(gè) User 對(duì)象需要轉(zhuǎn)換為 UserVo 對(duì)象:
@Data public class User { private String name; private int age; private String password; }
@Data public class UserVo { private String name; private int age; }
常規(guī)的有兩種方式:
- 使用 getter 和 setter 方法進(jìn)行賦值,但是這個(gè)方法有著大量枯燥且重復(fù)的工作,一旦出錯(cuò)也不易于發(fā)現(xiàn),可讀性差。
- 使用 spring 提供的
BeanUtils
工具類進(jìn)行對(duì)象之間的轉(zhuǎn)換,如下代碼塊所示,但是因?yàn)閮?nèi)部采用反射實(shí)現(xiàn),性能低下,出現(xiàn)問題時(shí)不容易調(diào)試。
// 創(chuàng)建一個(gè) User 對(duì)象 User user = new User(); user.setName("wen"); user.setAge(18); user.setPassword("123456"); // 創(chuàng)建一個(gè) UserVo 對(duì)象 UserVo userVo = new UserVo(); // 一行代碼實(shí)現(xiàn) user => userVo BeanUtils.copyProperties(user, userVo);
所以 MapStruct 應(yīng)運(yùn)而生,這個(gè)框架是基于 Java 注釋處理器,定義一個(gè)轉(zhuǎn)換接口,在編譯的時(shí)候會(huì)根據(jù)接口類和方法相關(guān)的注解,自動(dòng)生成實(shí)現(xiàn)類,底層是基于 getter 和 setter 方法的,比 BeanUtils
的性能要高。然而美中不足的是,當(dāng)需要轉(zhuǎn)換的對(duì)象較多或者結(jié)構(gòu)復(fù)雜的時(shí)候,需要定義較多的轉(zhuǎn)換接口和轉(zhuǎn)換方法。
此時(shí),就可以使用 MapStruct Plus ,一個(gè)注解就可以生成兩個(gè)類之間的轉(zhuǎn)換接口,使 Java 類型轉(zhuǎn)換更加便捷和優(yōu)雅。
二、MapStruct Plus 的快速開始
本文以 Spring Boot 項(xiàng)目為例,版本:
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 插件,配置項(xiàng)目的構(gòu)建過(guò)程(這一步非常非常重要?。。。?/strong>引入 Maven 插件,配置項(xiàng)目的構(gòu)建過(guò)程(這一步非常非常重要?。。。?/strong>引入 Maven 插件,配置項(xiàng)目的構(gòu)建過(guò)程(這一步非常非常重要?。。。?/strong>
<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 倉(cāng)庫(kù)地址
2. 指定對(duì)象映射關(guān)系
在 User 或者 UserVo 上面增加注解 —— @AutoMapper,并設(shè)置 target 為對(duì)方類。
以下面代碼舉例,添加注解:@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. 編寫測(cè)試代碼
@SpringBootTest public class QuickStartTest { @Autowired private Converter converter; @Test public void test() { // 創(chuàng)建 User 對(duì)象 User user = new User(); user.setUsername("wen"); user.setAge(18); user.setPassword("123456"); // 使用 MapStruct plus 進(jìn)行對(duì)象間轉(zhuǎn)換:User =》 UserVo UserVo userVo = converter.convert(user, UserVo.class); // 輸出轉(zhuǎn)換之后的對(duì)象 System.out.println(userVo); // 斷言測(cè)試 assert user.getUsername().equals(userVo.getUsername()); assert user.getAge() == userVo.getAge(); } }
4. 運(yùn)行結(jié)果
測(cè)試通過(guò),輸出:
5. 原理解析
通過(guò)以上示例可以看出,User 對(duì)象轉(zhuǎn)化為 UserVo 對(duì)象主要是UserVo userVo = converter.convert(user, UserVo.class);
這行代碼,其底層也很簡(jiǎn)單,原理是通過(guò) getter 和 setter 實(shí)現(xiàn)的:
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/實(shí)體類存放路徑
通過(guò)上圖,可以看到,哪怕沒有給 UserVo 實(shí)體類使用@AutoMapper
注解,MapStruct Plus 會(huì)自動(dòng)生成 User 轉(zhuǎn) UserVo 的接口和實(shí)現(xiàn)類,同時(shí)也會(huì)生成 UserVo 轉(zhuǎn)換為 User 的實(shí)體類和接口。
以上為重要規(guī)則,下面也能用得到?。?!
三、自定義實(shí)體類中的屬性轉(zhuǎn)換
在上面的例子中,兩個(gè)實(shí)體類中對(duì)應(yīng)的屬性都是同一種類型,那么想要自定義屬性比如:后端存儲(chǔ)的是字符串 String 類型的屬性,想給前端返回一個(gè) List 類型的屬性,可以根據(jù)規(guī)則進(jìn)行轉(zhuǎn)換。
下面的舉例是 String 屬性和 List 屬性之間的相互轉(zhuǎn)化(String 《===》List)
有兩種方式:
- 自定義一個(gè)類型轉(zhuǎn)換器,通過(guò)
@AutoMapper
的uses
屬性引入 - 通過(guò)
@AutoMapping
中配置的expression
表達(dá)式配置
1. 自定義一個(gè)類型轉(zhuǎn)換器
首先定義兩個(gè)類型轉(zhuǎn)換器,一個(gè)是 String 轉(zhuǎn)為 List,一個(gè)是 List 是 String。且兩個(gè)類型轉(zhuǎn)換器需要定義為 Spring 的 Bean,即使用 @Component
注解。
String 轉(zhuǎn)為 List 的轉(zhuǎn)換器:
@Component public class StringToListConverter { public List<String> stringToList(String str) { if (str == null) { return Collections.emptyList(); } return Arrays.asList(str.split(",")); } }
List 轉(zhuǎn)為 String 的轉(zhuǎn)換器:
@Component public class ListToStringConverter { public String listToString(List<String> list) { if (list == null || list.isEmpty()) { return null; } return String.join(",", list); } }
2. 使用類型轉(zhuǎn)換器
第二步,使用該類型轉(zhuǎn)換器,即在 @AutoMapper
注解中使用 uses,且給需要轉(zhuǎn)化的屬性加上 @AutoMapping
注解,target 指向另一個(gè)需要轉(zhuǎn)化的屬性。
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. 進(jìn)行測(cè)試
第三步,進(jìn)行測(cè)試。
@SpringBootTest public class QuickStartTest { @Autowired private Converter converter; @Test public void test() { // 創(chuàng)建一個(gè) User 對(duì)象 User user = new User(); user.setName("wen"); user.setAge(18); user.setPassword("123456"); user.setTags("Java,Python,C++"); // 轉(zhuǎn)換 UserVo userVo = converter.convert(user, UserVo.class); System.out.println(userVo); assert userVo.getTagList().size() == 3; } }
測(cè)試結(jié)果:
測(cè)試用例通過(guò),User 類中的 String 類型的 tags 屬性,成功轉(zhuǎn)化為 UserVo 類中的 List 類型的 tagList 屬性。
還有一種方法是直接在注解中寫表達(dá)式,但是博主覺得這種方式?jīng)]有自定義轉(zhuǎn)換器好,所以在本文中不列舉
如果感興趣,詳情請(qǐng)參考:表達(dá)式自定義屬性轉(zhuǎn)換
四、Map 轉(zhuǎn)為 Object
MapStruct Plus 提供了 Map<String, Object>
轉(zhuǎn)化為對(duì)象的功能。
轉(zhuǎn)換邏輯:針對(duì)目標(biāo)類中的一個(gè)屬性,首先會(huì)判斷 Map 中是否存在該鍵,如果存在的話,首先判斷類型,
- 如果類型相同,直接強(qiáng)轉(zhuǎn)
- 若果類型不同,會(huì)使用 Hutool 提供的類型轉(zhuǎn)換工具嘗試轉(zhuǎn)換為目標(biāo)類型
MapStruct Plus 在 1.4.0+ 版本取消了內(nèi)置 Hutool 框架,如果需要用到 Map 轉(zhuǎn)化為對(duì)象的功能時(shí),需要引入 hutool-core
這個(gè)依賴,最新版本查看:Hutool 依賴庫(kù)
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-core</artifactId> <version>5.8.29</version> </dependency>
1. 使用步驟
- 引入
hutool-core
依賴 - 在目標(biāo)類上添加
@AutoMapMapper
注解 - 同時(shí)支持自定義類作為屬性,需要在自定義類上增加
@AutoMapMapper
注解
2. 定義對(duì)象
為了更好的理解,直接用最復(fù)雜的 Map 轉(zhuǎn)對(duì)象的例子舉例,即內(nèi)部屬性既有基本類型,也有自定義的對(duì)象
定義一個(gè) Body 類,里面有身高體重屬性,定義一個(gè) Person 類,里面有基本信息和一個(gè) 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. 轉(zhuǎn)換測(cè)試
@SpringBootTest public class MapToObjectTest { @Autowired private Converter converter; @Test public void test() { // 創(chuàng)建一個(gè) Map,鍵是 Body 的屬性名,值是屬性值 Map<String, Object> map1 = new HashMap<>(); map1.put("height", 180); map1.put("weight", 150); // 創(chuàng)建第二個(gè) Map,鍵是 Person 的屬性名,值是屬性值 Map<String, Object> map2 = new HashMap<>(); map2.put("name", "wen"); map2.put("age", 18); map2.put("body", map1); // 將 Map2 轉(zhuǎn)化為 Person 對(duì)象 Person person = converter.convert(map2, Person.class); System.out.println(person); } }
測(cè)試成功,Map 對(duì)象成功轉(zhuǎn)化為 Person 對(duì)象:
五、枚舉類型轉(zhuǎn)換
枚舉類型的轉(zhuǎn)換,需要在枚舉類上添加 @AutoEnumMapper
注解,增加該注解后,在任意類型中需要轉(zhuǎn)換該枚舉時(shí)都可以自動(dòng)轉(zhuǎn)換。
使用 @AutoEnumMapper
注解的時(shí)候,需要注意:這個(gè)枚舉類必須要有一個(gè)可以保證唯一的字段,并將該字段添加到注解的 value
屬性中
1. 定義一個(gè)枚舉類
定義一個(gè)狀態(tài)枚舉類,唯一字段是 code,用來(lái)表示開始還是關(guān)閉:
@Getter @AllArgsConstructor @AutoEnumMapper("code") public enum StateEnum { ENABLE(1, "啟用"), DISABLE(0, "禁用"); private final int code; private final String desc; }
2. 定義要轉(zhuǎn)換的對(duì)象
定義一個(gè)保存枚舉類的類 Course,再定義一個(gè)需要轉(zhuǎn)換的 CourseVo 類:
Course 類:
@Data @AutoMapper(target = CourseVo.class) public class Course { private StateEnum state; }
CourseVo 類:
@Data public class CourseVo { private Integer state; }
3. 轉(zhuǎn)換測(cè)試
@SpringBootTest public class EnumToValueTest { @Autowired private Converter converter; @Test public void test() { // 創(chuàng)建一個(gè) Course 對(duì)象 Course course = new Course(); course.setState(StateEnum.ENABLE); // 將 Course 對(duì)象轉(zhuǎn)換為 CourseVo 對(duì)象 CourseVo courseVo = converter.convert(course, CourseVo.class); System.out.println(courseVo); // 將 CourseVo 對(duì)象轉(zhuǎn)換為 Course 對(duì)象 Course course1 = converter.convert(courseVo, Course.class); System.out.println(course1); } }
測(cè)試成功,Enum 可以轉(zhuǎn)化為整形,整形也可以轉(zhuǎn)化為 Enum:
4. 注意
枚舉和使用枚舉的類需要在同一個(gè)模塊(module)中。
當(dāng)枚舉與要使用的類型,不在同一個(gè)模塊中,是不能自動(dòng)轉(zhuǎn)換的,需要指定依賴關(guān)系。在 @AutoMapper
注解中,可以通過(guò) useEnums
來(lái)指定需要依賴的枚舉類列表。
六、一個(gè)類與多個(gè)類之間的轉(zhuǎn)換
MapStruct Plus 還支持一個(gè)類和多個(gè)類進(jìn)行轉(zhuǎn)換,可以通過(guò) @AutoMappers
來(lái)配置,該注解支持配置多個(gè) @AutoMapper
。
在配置多個(gè)類進(jìn)行轉(zhuǎn)化的時(shí)候,多個(gè)類可能有相同的屬性,為了解決屬性沖突的問題,可以使用 @AutoMappings
指定多個(gè)轉(zhuǎn)換規(guī)則,并且在使用 @AutoMapping
注解時(shí),配置 targetClass
屬性,指定當(dāng)前規(guī)則的目標(biāo)轉(zhuǎn)化類。
如果配置 @AutoMapping
注解時(shí),沒有指定 targetClass
,那么當(dāng)前規(guī)則就會(huì)應(yīng)用所有類轉(zhuǎn)換。
1. 定義對(duì)象
定義一個(gè) User
類,一個(gè) Course
類,一個(gè) 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. 轉(zhuǎn)換測(cè)試
@SpringBootTest public class OneToOthersTest { @Autowired private Converter converter; @Test public void test() { // 創(chuàng)建 User 對(duì)象 User user = new User(); user.setName("wen"); user.setAge(18); user.setPassword("123456"); user.setTags("Java,Python,Go,C++"); // 創(chuàng)建 Course 對(duì)象 Course course = new Course(); course.setName("Java 開發(fā)"); course.setTeacher("教 Java 的老師"); // 轉(zhuǎn)換(User 對(duì)象和 Course 對(duì)象)為 UserVo 對(duì)象 UserVo userVo = converter.convert(user, UserVo.class); userVo = converter.convert(course, userVo); System.out.println(userVo); // 轉(zhuǎn)換 UserVo 對(duì)象為(User 對(duì)象和 Course 對(duì)象) user = converter.convert(userVo, User.class); course = converter.convert(userVo, Course.class); System.out.println(user); System.out.println(course); } }
3. 測(cè)試結(jié)果
總結(jié)
本文使用大量示例詳細(xì)解釋了在 Spring Boot
項(xiàng)目開發(fā)中使用 MapStruct Plus
的方法,多加練習(xí)熟能生巧。技術(shù)沒有高低之分,不管是使用原始的 getter/setter
方法,還是使用 BeanUtils
,亦或者使用本文所介紹的 MapStruct Plus
,只要找到解決問題的合適方案就可以。
到此這篇關(guān)于MapStruct Plus的使用教程的文章就介紹到這了,更多相關(guān)MapStruct Plus內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決@Autowired報(bào)錯(cuò)Could not autowire. No bea
介紹了在IDEA中使用@Autowired報(bào)錯(cuò)Couldnot autowire. No beans of 'XXX' type found的解決方法,原因是@Autowired在注入service時(shí),由于service接口沒有實(shí)現(xiàn)類,而mybatis僅需提供Dao接口,導(dǎo)致@Autowired無(wú)法識(shí)別2024-12-12微信公眾號(hào)獲取access_token的方法實(shí)例分析
這篇文章主要介紹了微信公眾號(hào)獲取access_token的方法,結(jié)合實(shí)例形式分析了java實(shí)現(xiàn)微信公眾號(hào)獲取access_token的相關(guān)原理、實(shí)現(xiàn)方法及操作注意事項(xiàng),需要的朋友可以參考下2019-10-10Java 對(duì)稱加密幾種算法分別實(shí)現(xiàn)
這篇文章主要介紹了Java 對(duì)稱加密使用DES / 3DES / AES 這三種算法分別實(shí)現(xiàn)的相關(guān)資料,這里提供了實(shí)例代碼,需要的朋友可以參考下2017-01-01feign 如何獲取請(qǐng)求真實(shí)目的ip地址
這篇文章主要介紹了feign 獲取請(qǐng)求真實(shí)目的ip地址操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06