丟棄掉那些BeanUtils工具類吧,MapStruct真香?。?!
掃描二維碼
隨時隨地手機看文章
在前幾天的文章《為什么阿里巴巴禁止使用Apache Beanutils進行屬性的copy?》中,我曾經(jīng)對幾款屬性拷貝的工具類進行了對比。
然后在評論區(qū)有些讀者反饋說MapStruct才是真的香,于是我就抽時間了解了一下MapStruct。結果我發(fā)現(xiàn),這真的是一個神仙框架,炒雞香。
這一篇文章就來簡單介紹下MapStruct的用法,并且再和其他幾個工具類進行一下對比。
首先,我們先說一下MapStruct這類框架適用于什么樣的場景,為什么市面上會有這么多的類似的框架。
在軟件體系架構設計中,分層式結構是最常見,也是最重要的一種結構。很多人都對三層架構、四層架構等并不陌生。
甚至有人說:"計算機科學領域的任何問題都可以通過增加一個間接的中間層來解決,如果不行,那就加兩層。"
但是,隨著軟件架構分層越來越多,那么各個層次之間的數(shù)據(jù)模型就要面臨著相互轉(zhuǎn)換的問題,典型的就是我們可以在代碼中見到各種O,如DO、DTO、VO等。
一般情況下,同樣一個數(shù)據(jù)模型,我們在不同的層次要使用不同的數(shù)據(jù)模型。如在數(shù)據(jù)存儲層,我們使用DO來抽象一個業(yè)務實體;在業(yè)務邏輯層,我們使用DTO來表示數(shù)據(jù)傳輸對象;到了展示層,我們又把對象封裝成VO來與前端進行交互。
那么,數(shù)據(jù)的從前端透傳到數(shù)據(jù)持久化層(從持久層透傳到前端),就需要進行對象之間的互相轉(zhuǎn)化,即在不同的對象模型之間進行映射。
通常我們可以使用get/set等方式逐一進行字段映射操作,如:
personDTO.setName(personDO.getName());
personDTO.setAge(personDO.getAge());
personDTO.setSex(personDO.getSex());
personDTO.setBirthday(personDO.getBirthday());
但是,編寫這樣的映射代碼是一項冗長且容易出錯的任務。MapStruct等類似的框架的目標是通過自動化的方式盡可能多地簡化這項工作。
MapStruct(https://mapstruct.org/ )是一種代碼生成器,它極大地簡化了基于"約定優(yōu)于配置"方法的Java bean類型之間映射的實現(xiàn)。生成的映射代碼使用純方法調(diào)用,因此快速、類型安全且易于理解。
約定優(yōu)于配置,也稱作按約定編程,是一種軟 件設計范式,旨在減少軟件開發(fā)人員需做決定的數(shù)量,獲得簡單的好處,而又不失靈活性。
假設我們有兩個類需要進行互相轉(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進行bean映射。
想要使用MapStruct,首先需要依賴他的相關的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>
因為MapStruct需要在編譯器生成轉(zhuǎn)換代碼,所以需要在maven-compiler-plugin插件中配置上對mapstruct-processor的引用。這部分在后文會再次介紹。
之后,我們需要定義一個做映射的接口,主要代碼如下:
@Mapper
interface?PersonConverter?{
????PersonConverter?INSTANCE?=?Mappers.getMapper(PersonConverter.class);
????@Mappings(@Mapping(source?=?"name",?target?=?"userName"))
????PersonDTO?do2dto(PersonDO?person);
}
使用注解@Mapper定義一個Converter接口,在其中定義一個do2dto方法,方法的入?yún)㈩愋褪荘ersonDO,出參類型是PersonDTO,這個方法就用于將PersonDO轉(zhuǎn)成PersonDTO。
測試代碼如下:
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);
}
輸出結果:
PersonDTO{userName='Hollis',?age=26,?birthday=Sat?Aug?08?19:00:44?CST?2020,?gender=MALE}
可以看到,我們使用MapStruct完美的將PersonDO轉(zhuǎn)成了PersonDTO。
上面的代碼可以看出,MapStruct的用法比較簡單,主要依賴@Mapper注解。
但是我們知道,大多數(shù)情況下,我們需要互相轉(zhuǎn)換的兩個類之間的屬性名稱、類型等并不完全一致,還有些情況我們并不想直接做映射,那么該如何處理呢?
其實MapStruct在這方面也是做的很好的。
首先,可以明確的告訴大家,如果要轉(zhuǎn)換的兩個類中源對象屬性與目標對象屬性的類型和名字一致的時候,會自動映射對應屬性。
那么,如果遇到特殊情況如何處理呢?
名字不一致如何映射
如上面的例子中,在PersonDO中用name表示用戶名稱,而在PersonDTO中使用userName表示用戶名,那么如何進行參數(shù)映射呢。
這時候就要使用@Mapping注解了,只需要在方法簽名上,使用該注解,并指明需要轉(zhuǎn)換的源對象的名字和目標對象的名字就可以了,如將name的值映射給userName,可以使用如下方式:
@Mapping(source?=?"name",?target?=?"userName")
可以自動映射的類型
除了名字不一致以外,還有一種特殊情況,那就是類型不一致,如上面的例子中,在PersonDO中用String類型表示用戶性別,而在PersonDTO中使用一個Genter的枚舉表示用戶性別。
這時候類型不一致,就需要涉及到互相轉(zhuǎn)換的問題
其實,MapStruct會對部分類型自動做映射,不需要我們做額外配置,如例子中我們將String類型自動轉(zhuǎn)成了枚舉類型。
一般情況下,對于以下情況可以做自動類型轉(zhuǎn)換:
-
基本類型及其他們對應的包裝類型。 -
基本類型的包裝類型和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 引入的新的語言特性,用關鍵字default來標注,被default所標注的方法,需要提供實現(xiàn),而子類可以選擇實現(xiàn)或者不實現(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;
????}
}
強烈推薦,真的很香?。?!
—————END—————
喜歡本文的朋友,歡迎關注公眾號?程序員小灰,收看更多精彩內(nèi)容
點個[在看],是對小灰最大的支持!
免責聲明:本文內(nèi)容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!