丟棄掉那些BeanUtils工具類吧,MapStruct真香?。?!
掃描二維碼
隨時(shí)隨地手機(jī)看文章
在前幾天的文章《為什么阿里巴巴禁止使用Apache Beanutils進(jìn)行屬性的copy?》中,我曾經(jīng)對(duì)幾款屬性拷貝的工具類進(jìn)行了對(duì)比。
然后在評(píng)論區(qū)有些讀者反饋說MapStruct才是真的香,于是我就抽時(shí)間了解了一下MapStruct。結(jié)果我發(fā)現(xiàn),這真的是一個(gè)神仙框架,炒雞香。
這一篇文章就來簡(jiǎn)單介紹下MapStruct的用法,并且再和其他幾個(gè)工具類進(jìn)行一下對(duì)比。
首先,我們先說一下MapStruct這類框架適用于什么樣的場(chǎng)景,為什么市面上會(huì)有這么多的類似的框架。
在軟件體系架構(gòu)設(shè)計(jì)中,分層式結(jié)構(gòu)是最常見,也是最重要的一種結(jié)構(gòu)。很多人都對(duì)三層架構(gòu)、四層架構(gòu)等并不陌生。
甚至有人說:"計(jì)算機(jī)科學(xué)領(lǐng)域的任何問題都可以通過增加一個(gè)間接的中間層來解決,如果不行,那就加兩層。"
但是,隨著軟件架構(gòu)分層越來越多,那么各個(gè)層次之間的數(shù)據(jù)模型就要面臨著相互轉(zhuǎn)換的問題,典型的就是我們可以在代碼中見到各種O,如DO、DTO、VO等。
一般情況下,同樣一個(gè)數(shù)據(jù)模型,我們?cè)诓煌膶哟我褂貌煌臄?shù)據(jù)模型。如在數(shù)據(jù)存儲(chǔ)層,我們使用DO來抽象一個(gè)業(yè)務(wù)實(shí)體;在業(yè)務(wù)邏輯層,我們使用DTO來表示數(shù)據(jù)傳輸對(duì)象;到了展示層,我們又把對(duì)象封裝成VO來與前端進(jìn)行交互。
那么,數(shù)據(jù)的從前端透?jìng)鞯綌?shù)據(jù)持久化層(從持久層透?jìng)鞯角岸耍托枰M(jìn)行對(duì)象之間的互相轉(zhuǎn)化,即在不同的對(duì)象模型之間進(jìn)行映射。
通常我們可以使用get/set等方式逐一進(jìn)行字段映射操作,如:
personDTO.setName(personDO.getName());
personDTO.setAge(personDO.getAge());
personDTO.setSex(personDO.getSex());
personDTO.setBirthday(personDO.getBirthday());
但是,編寫這樣的映射代碼是一項(xiàng)冗長(zhǎng)且容易出錯(cuò)的任務(wù)。MapStruct等類似的框架的目標(biāo)是通過自動(dòng)化的方式盡可能多地簡(jiǎn)化這項(xiàng)工作。
MapStruct(https://mapstruct.org/ )是一種代碼生成器,它極大地簡(jiǎn)化了基于"約定優(yōu)于配置"方法的Java bean類型之間映射的實(shí)現(xiàn)。生成的映射代碼使用純方法調(diào)用,因此快速、類型安全且易于理解。
約定優(yōu)于配置,也稱作按約定編程,是一種軟 件設(shè)計(jì)范式,旨在減少軟件開發(fā)人員需做決定的數(shù)量,獲得簡(jiǎn)單的好處,而又不失靈活性。
假設(shè)我們有兩個(gè)類需要進(jìn)行互相轉(zhuǎn)換,分別是PersonDO和PersonDTO,類定義如下:
public?class?PersonDO?{
????private?Integer?id;
????private?String?name;
????private?int?age;
????private?Date?birthday;
????private?String?gender;
}
public?class?PersonDTO?{
????private?String?userName;
????private?Integer?age;
????private?Date?birthday;
????private?Gender?gender;
}
我們演示下如何使用MapStruct進(jìn)行bean映射。
想要使用MapStruct,首先需要依賴他的相關(guān)的jar包,使用maven依賴方式如下:
...
<properties>
????<org.mapstruct.version>1.3.1.Finalorg.mapstruct.version>
properties>
...
<dependencies>
????<dependency>
????????<groupId>org.mapstructgroupId>
????????<artifactId>mapstructartifactId>
????????<version>${org.mapstruct.version}version>
????dependency>
dependencies>
...
<build>
????<plugins>
????????<plugin>
????????????<groupId>org.apache.maven.pluginsgroupId>
????????????<artifactId>maven-compiler-pluginartifactId>
????????????<version>3.8.1version>
????????????<configuration>
????????????????<source>1.8source>?
????????????????<target>1.8target>?
????????????????<annotationProcessorPaths>
????????????????????<path>
????????????????????????<groupId>org.mapstructgroupId>
????????????????????????<artifactId>mapstruct-processorartifactId>
????????????????????????<version>${org.mapstruct.version}version>
????????????????????path>
????????????????????
????????????????annotationProcessorPaths>
????????????configuration>
????????plugin>
????plugins>
build>
因?yàn)镸apStruct需要在編譯器生成轉(zhuǎn)換代碼,所以需要在maven-compiler-plugin插件中配置上對(duì)mapstruct-processor的引用。這部分在后文會(huì)再次介紹。
之后,我們需要定義一個(gè)做映射的接口,主要代碼如下:
@Mapper
interface?PersonConverter?{
????PersonConverter?INSTANCE?=?Mappers.getMapper(PersonConverter.class);
????@Mappings(@Mapping(source?=?"name",?target?=?"userName"))
????PersonDTO?do2dto(PersonDO?person);
}
使用注解@Mapper定義一個(gè)Converter接口,在其中定義一個(gè)do2dto方法,方法的入?yún)㈩愋褪荘ersonDO,出參類型是PersonDTO,這個(gè)方法就用于將PersonDO轉(zhuǎn)成PersonDTO。
測(cè)試代碼如下:
public?static?void?main(String[]?args)?{
????PersonDO?personDO?=?new?PersonDO();
????personDO.setName("Hollis");
????personDO.setAge(26);
????personDO.setBirthday(new?Date());
????personDO.setId(1);
????personDO.setGender(Gender.MALE.name());
????PersonDTO?personDTO?=?PersonConverter.INSTANCE.do2dto(personDO);
????System.out.println(personDTO);
}
輸出結(jié)果:
PersonDTO{userName='Hollis',?age=26,?birthday=Sat?Aug?08?19:00:44?CST?2020,?gender=MALE}
可以看到,我們使用MapStruct完美的將PersonDO轉(zhuǎn)成了PersonDTO。
上面的代碼可以看出,MapStruct的用法比較簡(jiǎn)單,主要依賴@Mapper注解。
但是我們知道,大多數(shù)情況下,我們需要互相轉(zhuǎn)換的兩個(gè)類之間的屬性名稱、類型等并不完全一致,還有些情況我們并不想直接做映射,那么該如何處理呢?
其實(shí)MapStruct在這方面也是做的很好的。
首先,可以明確的告訴大家,如果要轉(zhuǎn)換的兩個(gè)類中源對(duì)象屬性與目標(biāo)對(duì)象屬性的類型和名字一致的時(shí)候,會(huì)自動(dòng)映射對(duì)應(yīng)屬性。
那么,如果遇到特殊情況如何處理呢?
名字不一致如何映射
如上面的例子中,在PersonDO中用name表示用戶名稱,而在PersonDTO中使用userName表示用戶名,那么如何進(jìn)行參數(shù)映射呢。
這時(shí)候就要使用@Mapping注解了,只需要在方法簽名上,使用該注解,并指明需要轉(zhuǎn)換的源對(duì)象的名字和目標(biāo)對(duì)象的名字就可以了,如將name的值映射給userName,可以使用如下方式:
@Mapping(source?=?"name",?target?=?"userName")
可以自動(dòng)映射的類型
除了名字不一致以外,還有一種特殊情況,那就是類型不一致,如上面的例子中,在PersonDO中用String類型表示用戶性別,而在PersonDTO中使用一個(gè)Genter的枚舉表示用戶性別。
這時(shí)候類型不一致,就需要涉及到互相轉(zhuǎn)換的問題
其實(shí),MapStruct會(huì)對(duì)部分類型自動(dòng)做映射,不需要我們做額外配置,如例子中我們將String類型自動(dòng)轉(zhuǎn)成了枚舉類型。
一般情況下,對(duì)于以下情況可以做自動(dòng)類型轉(zhuǎn)換:
-
基本類型及其他們對(duì)應(yīng)的包裝類型。 -
基本類型的包裝類型和String類型之間 -
String類型和枚舉類型之間
自定義常量
@Mapping(source?=?"name",?constant?=?"hollis")
類型不一致的如何映射
public?class?PersonDO?{
????private?String?name;
????private?String?address;
}
public?class?PersonDTO?{
????private?String?userName;
????private?HomeAddress?address;
}
@Mapper
interface?PersonConverter?{
????PersonConverter?INSTANCE?=?Mappers.getMapper(PersonConverter.class);
????@Mapping(source?=?"userName",?target?=?"name")
????@Mapping(target?=?"address",expression?=?"java(homeAddressToString(dto2do.getAddress()))")
????PersonDO?dto2do(PersonDTO?dto2do);
????default?String?homeAddressToString(HomeAddress?address){
????????return?JSON.toJSONString(address);
????}
}
default方法: Java 8 引入的新的語言特性,用關(guān)鍵字default來標(biāo)注,被default所標(biāo)注的方法,需要提供實(shí)現(xiàn),而子類可以選擇實(shí)現(xiàn)或者不實(shí)現(xiàn)該方法
@Mapping(target?=?"address",expression?=?"java(homeAddressToString(dto2do.getAddress()))")
@Mapping(target?=?"birthday",dateFormat?=?"yyyy-MM-dd?HH:mm:ss")
@Mapper
interface?PersonConverter?{
????PersonConverter?INSTANCE?=?Mappers.getMapper(PersonConverter.class);
????@Mapping(source?=?"userName",?target?=?"name")
????@Mapping(target?=?"address",expression?=?"java(homeAddressToString(dto2do.getAddress()))")
????@Mapping(target?=?"birthday",dateFormat?=?"yyyy-MM-dd?HH:mm:ss")
????PersonDO?dto2do(PersonDTO?dto2do);
????default?String?homeAddressToString(HomeAddress?address){
????????return?JSON.toJSONString(address);
????}
}
@Generated(
????value?=?"org.mapstruct.ap.MappingProcessor",
????date?=?"2020-08-09T12:58:41+0800",
????comments?=?"version:?1.3.1.Final,?compiler:?javac,?environment:?Java?1.8.0_181?(Oracle?Corporation)"
)
class?PersonConverterImpl?implements?PersonConverter?{
????@Override
????public?PersonDO?dto2do(PersonDTO?dto2do)?{
????????if?(?dto2do?==?null?)?{
????????????return?null;
????????}
????????PersonDO?personDO?=?new?PersonDO();
????????personDO.setName(?dto2do.getUserName()?);
????????if?(?dto2do.getAge()?!=?null?)?{
????????????personDO.setAge(?dto2do.getAge()?);
????????}
????????if?(?dto2do.getGender()?!=?null?)?{
????????????personDO.setGender(?dto2do.getGender().name()?);
????????}
????????personDO.setAddress(?homeAddressToString(dto2do.getAddress())?);
????????return?personDO;
????}
}
強(qiáng)烈推薦,真的很香?。?!
—————END—————
喜歡本文的朋友,歡迎關(guān)注公眾號(hào)?程序員小灰,收看更多精彩內(nèi)容
點(diǎn)個(gè)[在看],是對(duì)小灰最大的支持!
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!