詳解Java對(duì)象轉(zhuǎn)換神器MapStruct庫(kù)的使用
前言
在我們?nèi)粘i_(kāi)發(fā)的程序中,為了各層之間解耦,一般會(huì)定義不同的對(duì)象用來(lái)在不同層之間傳遞數(shù)據(jù),比如xxxDTO、xxxVO、xxxQO,當(dāng)在不同層之間傳輸數(shù)據(jù)時(shí),不可避免地經(jīng)常需要將這些對(duì)象進(jìn)行相互轉(zhuǎn)換。
今天給大家介紹一個(gè)對(duì)象轉(zhuǎn)換工具M(jìn)apStruct,代碼簡(jiǎn)潔安全、性能高,強(qiáng)烈推薦。
MapStruct簡(jiǎn)介
MapStruct是一個(gè)代碼生成器,它基于約定優(yōu)于配置,極大地簡(jiǎn)化了Java Bean類(lèi)型之間映射的實(shí)現(xiàn)。特點(diǎn)如下:
- 基于注解
- 在編譯期自動(dòng)生成映射轉(zhuǎn)換代碼
- 類(lèi)型安全、高性能、無(wú)依賴性、易于理解閱讀
MapStruct入門(mén)
1. 引入依賴
這里使用Gradle構(gòu)建
dependencies?{ ????implementation?'org.mapstruct:mapstruct:1.4.2.Final' ????annotationProcessor?'org.mapstruct:mapstruct-processor:1.4.2.Final' }
2. 需要轉(zhuǎn)換的對(duì)象
創(chuàng)建兩個(gè)示例對(duì)象(e.g. 將Demo對(duì)象轉(zhuǎn)換為DemoDto對(duì)象)
/** ?*?源對(duì)象 ?*/ @Data public?class?Demo?{ ????private?Integer?id; ????private?String?name; } /** ?*?目標(biāo)對(duì)象 ?*/ @Data public?class?DemoDto?{ ????private?Integer?id; ????private?String?name; }
3. 創(chuàng)建轉(zhuǎn)換器
只需要?jiǎng)?chuàng)建一個(gè)轉(zhuǎn)換器接口類(lèi),并在類(lèi)上添加 @Mapper 注解即可(官方示例推薦以 xxxMapper 格式命名轉(zhuǎn)換器名稱)
@Mapper public?interface?DemoMapper?{ ????//使用Mappers工廠獲取DemoMapper實(shí)現(xiàn)類(lèi) ????DemoMapper?INSTANCE?=?Mappers.getMapper(DemoMapper.class); ????//定義接口方法,參數(shù)為來(lái)源對(duì)象,返回值為目標(biāo)對(duì)象 ????DemoDto?toDemoDto(Demo?demo); }
4. 驗(yàn)證
public?static?void?main(String[]?args)?{ ????Demo?demo?=?new?Demo(); ????demo.setId(111); ????demo.setName("hello"); ????DemoDto?demoDto?=?DemoMapper.INSTANCE.toDemoDto(demo); ????System.out.println("目標(biāo)對(duì)象demoDto為:"?+?demoDto); ????//輸出結(jié)果:目標(biāo)對(duì)象demoDto為:DemoDto(id=111, name=hello) }
測(cè)試結(jié)果如下:
目標(biāo)對(duì)象demoDto為:DemoDto(id=111, name=hello)
達(dá)到了我們的預(yù)期結(jié)果。
5. 自動(dòng)生成的實(shí)現(xiàn)類(lèi)
為什么聲明一個(gè)接口就可以轉(zhuǎn)換對(duì)象呢?我們看一下MapStruct在編譯期間自動(dòng)生成的實(shí)現(xiàn)類(lèi):
@Generated( ????value?=?"org.mapstruct.ap.MappingProcessor", ????date?=?"2022-09-01T17:54:38+0800", ????comments?=?"version:?1.4.2.Final,?compiler:?IncrementalProcessingEnvironment?from?gradle-language-java-7.3.jar,?environment:?Java?1.8.0_231?(Oracle?Corporation)" ) public?class?DemoMapperImpl?implements?DemoMapper?{ ????@Override ????public?DemoDto?toDemoDto(Demo?demo)?{ ????????if?(?demo?==?null?)?{ ????????????return?null; ????????} ????????DemoDto?demoDto?=?new?DemoDto(); ????????demoDto.setId(?demo.getId()?); ????????demoDto.setName(?demo.getName()?); ????????return?demoDto; ????} }
可以看到,MapStruct幫我們將繁雜的代碼自動(dòng)生成了,而且實(shí)現(xiàn)類(lèi)中用的都是最基本的get、set方法,易于閱讀理解,轉(zhuǎn)換速度非???。
MapStruct進(jìn)階
上面的例子只是小試牛刀,下面開(kāi)始展示MapStruct的強(qiáng)大之處。
(限于篇幅,這里不展示自動(dòng)生成的實(shí)現(xiàn)類(lèi)和驗(yàn)證結(jié)果,大家可自行測(cè)試)
場(chǎng)景1:屬性名稱不同、(基本)類(lèi)型不同
- 屬性名稱不同: 在方法上加上 @Mapping 注解,用來(lái)映射屬性
- 屬性基本類(lèi)型不同: 基本類(lèi)型和String等類(lèi)型會(huì)自動(dòng)轉(zhuǎn)換
關(guān)鍵字:@Mapping注解
/** ?*?來(lái)源對(duì)象 ?*/ @Data public?class?Demo?{ ????private?Integer?id; ????private?String?name; } /** ?*?目標(biāo)對(duì)象 ?*/ @Data public?class?DemoDto?{ ????private?String?id; ????private?String?fullname; } /** ?*?轉(zhuǎn)換器 ?*/ @Mapper public?interface?DemoMapper?{ ????DemoMapper?INSTANCE?=?Mappers.getMapper(DemoMapper.class); ????@Mapping(target?=?"fullname",?source?=?"name") ????DemoDto?toDemoDto(Demo?demo); }
場(chǎng)景2:統(tǒng)一映射不同類(lèi)型
下面例子中,time1、time2、time3都會(huì)被轉(zhuǎn)換,具體說(shuō)明看下面的注釋?zhuān)?/p>
/** ?*?來(lái)源對(duì)象 ?*/ @Data public?class?Demo?{ ????private?Integer?id; ????private?String?name; ????/** ?????*?time1、time2名稱相同,time3轉(zhuǎn)為time33 ?????*?這里的time1、time2、time33都是Date類(lèi)型 ?????*/ ????private?Date?time1; ????private?Date?time2; ????private?Date?time3; } /** ?*?目標(biāo)對(duì)象 ?*/ @Data public?class?DemoDto?{ ????private?String?id; ????private?String?name; ????/** ?????*?這里的time1、time2、time33都是String類(lèi)型 ?????*/ ????private?String?time1; ????private?String?time2; ????private?String?time33; } /** ?*?轉(zhuǎn)換器 ?*/ @Mapper public?interface?DemoMapper?{ ????DemoMapper?INSTANCE?=?Mappers.getMapper(DemoMapper.class); ????@Mapping(target?=?"time33",?source?=?"time3") ????DemoDto?toDemoDto(Demo?demo); ???? ????//MapStruct會(huì)將所有匹配到的: ????//源類(lèi)型為Date、目標(biāo)類(lèi)型為String的屬性, ????//按以下方法進(jìn)行轉(zhuǎn)換 ????static?String?date2String(Date?date)?{ ????????SimpleDateFormat?simpleDateFormat?=?new?SimpleDateFormat("yyyy-MM-dd?HH:mm:ss"); ????????String?strDate?=?simpleDateFormat.format(date); ????????return?strDate; ????} }
場(chǎng)景3:固定值、忽略某個(gè)屬性、時(shí)間轉(zhuǎn)字符串格式
一個(gè)例子演示三種用法,具體說(shuō)明看注釋?zhuān)苋菀桌斫猓?/p>
關(guān)鍵字:ignore、constant、dateFormat
/** ?*?來(lái)源對(duì)象 ?*/ @Data public?class?Demo?{ ????private?Integer?id; ????private?String?name; ????private?Date?time; } /** ?*?目標(biāo)對(duì)象 ?*/ @Data public?class?DemoDto?{ ????private?String?id; ????private?String?name; ????private?String?time; } /** ?*?轉(zhuǎn)換器 ?*/ @Mapper public?interface?DemoMapper?{ ????DemoMapper?INSTANCE?=?Mappers.getMapper(DemoMapper.class); ????//id屬性不賦值 ????@Mapping(target?=?"id",?ignore?=?true) ????//name屬性固定賦值為“hello” ????@Mapping(target?=?"name",?constant?=?"hello") ????//time屬性轉(zhuǎn)為yyyy-MM-dd?HH:mm:ss格式的字符串 ????@Mapping(target?=?"time",?dateFormat?=?"yyyy-MM-dd?HH:mm:ss") ????DemoDto?toDemoDto(Demo?demo); }
場(chǎng)景4:為某個(gè)屬性指定轉(zhuǎn)換方法
場(chǎng)景2中,我們是按照某個(gè)轉(zhuǎn)換方法,統(tǒng)一將一種類(lèi)型轉(zhuǎn)換為另外一種類(lèi)型;而下面這個(gè)例子,是為某個(gè)屬性指定方法:
關(guān)鍵字:@Named注解、qualifiedByName
/** ?*?來(lái)源對(duì)象 ?*/ @Data public?class?Demo?{ ????private?Integer?id; ????private?String?name; } /** ?*?目標(biāo)對(duì)象 ?*/ @Data public?class?DemoDto?{ ????private?String?id; ????private?String?name; } /** ?*?轉(zhuǎn)換器 ?*/ @Mapper public?interface?DemoMapper?{ ????DemoMapper?INSTANCE?=?Mappers.getMapper(DemoMapper.class); ????//為name屬性指定@Named為convertName的方法進(jìn)行轉(zhuǎn)換 ????@Mapping(target?=?"name",?qualifiedByName?=?"convertName") ????DemoDto?toDemoDto(Demo?demo); ????@Named("convertName") ????static?String?aaa(String?name)?{ ????????return?"姓名為:"?+?name; ????} }
場(chǎng)景5:多個(gè)參數(shù)合并為一個(gè)對(duì)象
如果參數(shù)為多個(gè)的話,@Mapping注解中的source就要指定是哪個(gè)參數(shù)了,用點(diǎn)分隔:
關(guān)鍵字:點(diǎn)(.)
/** ?*?來(lái)源對(duì)象 ?*/ @Data public?class?Demo?{ ????private?Integer?id; ????private?String?name; } /** ?*?目標(biāo)對(duì)象 ?*/ @Data public?class?DemoDto?{ ????private?String?fullname; ????private?String?timestamp; } /** ?*?轉(zhuǎn)換器 ?*/ @Mapper public?interface?DemoMapper?{ ????DemoMapper?INSTANCE?=?Mappers.getMapper(DemoMapper.class); ????//fullname屬性賦值demo對(duì)象的name屬性(注意這里.的用法) ????//timestamp屬性賦值為傳入的time參數(shù) ????@Mapping(target?=?"fullname",?source?=?"demo.name") ????@Mapping(target?=?"timestamp",?source?=?"time") ????DemoDto?toDemoDto(Demo?demo,?String?time); }
場(chǎng)景6:已有目標(biāo)對(duì)象,將源對(duì)象屬性覆蓋到目標(biāo)對(duì)象
覆蓋目標(biāo)對(duì)象屬性時(shí),一般null值不覆蓋,所以需要在類(lèi)上的@Mapper注解中添加屬性:nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE 代表null值不進(jìn)行賦值。
關(guān)鍵字:@MappingTarget注解、nullValuePropertyMappingStrategy
/** ?*?來(lái)源對(duì)象 ?*/ @Data public?class?Demo?{ ????private?Integer?id; ????private?String?name; } /** ?*?目標(biāo)對(duì)象 ?*/ @Data public?class?DemoDto?{ ????private?String?id; ????private?String?name; } /** ?*?轉(zhuǎn)換器 ?*/ @Mapper(unmappedTargetPolicy?=?ReportingPolicy.IGNORE, ????????nullValuePropertyMappingStrategy?=?NullValuePropertyMappingStrategy.IGNORE) public?interface?DemoMapper?{ ????DemoMapper?INSTANCE?=?Mappers.getMapper(DemoMapper.class); ????//將已有的目標(biāo)對(duì)象當(dāng)作一個(gè)參數(shù)傳進(jìn)來(lái) ????DemoDto?toDemoDto(Demo?demo,?@MappingTarget?DemoDto?dto); }
場(chǎng)景7:源對(duì)象兩個(gè)屬性合并為一個(gè)屬性
這種情況可以使用@AfterMapping注解。
關(guān)鍵字:@AfterMapping注解、@MappingTarget注解
/** ?*?來(lái)源對(duì)象 ?*/ @Data public?class?Demo?{ ????private?Integer?id; ????private?String?firstName; ????private?String?lastName; } /** ?*?目標(biāo)對(duì)象 ?*/ @Data public?class?DemoDto?{ ????private?String?id; ????private?String?name; } /** ?*?轉(zhuǎn)換器 ?*/ @Mapper public?interface?DemoMapper?{ ????DemoMapper?INSTANCE?=?Mappers.getMapper(DemoMapper.class); ????DemoDto?toDemoDto(Demo?demo); ????//在轉(zhuǎn)換完成后執(zhí)行的方法,一般用到源對(duì)象兩個(gè)屬性合并為一個(gè)屬性的場(chǎng)景 ????//需要將源對(duì)象、目標(biāo)對(duì)象(@MappingTarget)都作為參數(shù)傳進(jìn)來(lái), ????@AfterMapping ????static?void?afterToDemoDto(Demo?demo,?@MappingTarget?DemoDto?demoDto)?{ ????????String?name?=?demo.getFirstName()?+?demo.getLastName(); ????????demoDto.setName(name); ????} }
小結(jié)
本文介紹了對(duì)象轉(zhuǎn)換工具 MapStruct 庫(kù),以安全、簡(jiǎn)潔、優(yōu)雅的方式來(lái)優(yōu)化我們的轉(zhuǎn)換代碼。
從文中的示例場(chǎng)景中可以看出,MapStruct 提供了大量的功能和配置,使我們可以快捷的創(chuàng)建出各種或簡(jiǎn)單或復(fù)雜的映射器。而這些,也只是 MapStruct 庫(kù)的冰山一角,還有很多強(qiáng)大的功能文中沒(méi)有提到,感興趣的朋友可以自行查看官方文檔。
到此這篇關(guān)于詳解Java對(duì)象轉(zhuǎn)換神器MapStruct庫(kù)的使用的文章就介紹到這了,更多相關(guān)Java MapStruct內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Spring中@Valid和@Validated注解用法
本文將以新增一個(gè)員工為功能切入點(diǎn),以常規(guī)寫(xiě)法為背景,慢慢烘托出?@Valid?和?@Validated?注解用法詳解,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-07-07聊聊SpringBoot中組件無(wú)法被注入的問(wèn)題
這篇文章主要介紹了SpringBoot中組件無(wú)法被注入的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08springboot+vue實(shí)現(xiàn)websocket配置過(guò)程解析
這篇文章主要介紹了springboot+vue實(shí)現(xiàn)websocket配置過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04Maven中dependency和plugins的繼承與約束
這篇文章主要介紹了Maven中dependency和plugins的繼承與約束,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12kafka生產(chǎn)者和消費(fèi)者的javaAPI的示例代碼
這篇文章主要介紹了kafka生產(chǎn)者和消費(fèi)者的javaAPI的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06SpringBoot3整合Druid監(jiān)控功能的項(xiàng)目實(shí)踐
Druid連接池作為一款強(qiáng)大的數(shù)據(jù)庫(kù)連接池,提供了豐富的監(jiān)控和管理功能,成為很多Java項(xiàng)目的首選,本文主要介紹了SpringBoot3整合Druid監(jiān)控功能的項(xiàng)目實(shí)踐,感興趣的可以了解一下2024-01-01