<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          丟棄掉那些BeanUtils工具類吧,MapStruct真香!!!

          共 7488字,需瀏覽 15分鐘

           ·

          2020-08-11 21:22

          △Hollis, 一個(gè)對(duì)Coding有著獨(dú)特追求的人△
          這是Hollis的第?298?篇原創(chuàng)分享
          作者 l Hollis
          來源 l Hollis(ID:hollischuang)

          在前幾天的文章《為什么阿里巴巴禁止使用Apache Beanutils進(jìn)行屬性的copy?》中,我曾經(jīng)對(duì)幾款屬性拷貝的工具類進(jìn)行了對(duì)比。

          然后在評(píng)論區(qū)有些讀者反饋說MapStruct才是真的香,于是我就抽時(shí)間了解了一下MapStruct。結(jié)果我發(fā)現(xiàn),這真的是一個(gè)神仙框架,炒雞香。

          這一篇文章就來簡單介紹下MapStruct的用法,并且再和其他幾個(gè)工具類進(jìn)行一下對(duì)比。




          為什么需要MapStruct ?

          首先,我們先說一下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)鞯角岸耍?,就需要進(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)冗長且容易出錯(cuò)的任務(wù)。MapStruct等類似的框架的目標(biāo)是通過自動(dòng)化的方式盡可能多地簡化這項(xiàng)工作。




          MapStruct的使用

          MapStruct(https://mapstruct.org/ )是一種代碼生成器,它極大地簡化了基于"約定優(yōu)于配置"方法的Java bean類型之間映射的實(shí)現(xiàn)。生成的映射代碼使用純方法調(diào)用,因此快速、類型安全且易于理解。

          約定優(yōu)于配置,也稱作按約定編程,是一種軟件設(shè)計(jì)范式,旨在減少軟件開發(fā)人員需做決定的數(shù)量,獲得簡單的好處,而又不失靈活性。

          假設(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的用法比較簡單,主要依賴@Mapper注解。

          但是我們知道,大多數(shù)情況下,我們需要互相轉(zhuǎn)換的兩個(gè)類之間的屬性名稱、類型等并不完全一致,還有些情況我們并不想直接做映射,那么該如何處理呢?

          其實(shí)MapStruct在這方面也是做的很好的。




          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類型和枚舉類型之間

          自定義常量

          如果我們?cè)谵D(zhuǎn)換映射過程中,想要給一些屬性定義一個(gè)固定的值,這個(gè)時(shí)候可以使用 constant

          @Mapping(source?=?"name",?constant?=?"hollis")

          類型不一致的如何映射

          還是上面的例子,如果我們需要在Person這個(gè)對(duì)象中增加家庭住址這個(gè)屬性,那么我們一般在PersonoDTO中會(huì)單獨(dú)定義一個(gè)HomeAddress類來表示家庭住址,而在Person類中,我們一般使用String類型表示家庭住址。
          這就需要在HomeAddress和String之間使用JSON進(jìn)行互相轉(zhuǎn)化,這種情況下,MapStruct也是可以支持的。

          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);

          ????}

          }

          我們只需要在PersonConverter中在定義一個(gè)方法(因?yàn)镻ersonConverter是一個(gè)接口,所以在JDK 1.8以后的版本中可以定義一個(gè)default方法),這個(gè)方法的作用就是將HomeAddress轉(zhuǎn)換成String類型。
          default方法:Java 8 引入的新的語言特性,用關(guān)鍵字default來標(biāo)注,被default所標(biāo)注的方法,需要提供實(shí)現(xiàn),而子類可以選擇實(shí)現(xiàn)或者不實(shí)現(xiàn)該方法
          然后在dto2do方法上,通過以下注解方式即可實(shí)現(xiàn)類型的轉(zhuǎn)換:

          @Mapping(target?=?"address",expression?=?"java(homeAddressToString(dto2do.getAddress()))")

          上面這種是自定義的類型轉(zhuǎn)換,還有一些類型的轉(zhuǎn)換是MapStruct本身就支持的,如String和Date之間的轉(zhuǎn)換:

          @Mapping(target?=?"birthday",dateFormat?=?"yyyy-MM-dd?HH:mm:ss")

          以上,簡單介紹了一些常用的字段映射的方法,也是我自己在工作中經(jīng)常遇到的幾個(gè)場(chǎng)景,更多的情況大家可以查看官方的示例(https://github.com/mapstruct/mapstruct-examples)。



          MapStruct的性能
          前面說了這么多MapStruct的用法,可以看出MapStruct的使用還是比較簡單的,并且字段映射上面的功能很強(qiáng)大,那么他的性能到底怎么樣呢?
          參考《為什么阿里巴巴禁止使用Apache Beanutils進(jìn)行屬性的copy?》中的示例,我們對(duì)MapStruct進(jìn)行性能測(cè)試。
          分別執(zhí)行1000、10000、100000、1000000次映射的耗時(shí)分別為:0ms、1ms、3ms、6ms。
          可以看到,MapStruct的耗時(shí)相比較于其他幾款工具來說是非常短的
          那么,為什么MapStruct的性能可以這么好呢?
          其實(shí),MapStruct和其他幾類框架最大的區(qū)別就是:與其他映射框架相比,MapStruct在編譯時(shí)生成bean映射,這確保了高性能,可以提前將問題反饋出來,也使得開發(fā)人員可以徹底的錯(cuò)誤檢查。
          還記得前面我們?cè)谝隡apStruct的依賴的時(shí)候,特別在maven-compiler-plugin中增加了mapstruct-processor的支持嗎?
          并且我們?cè)诖a中使用了很多MapStruct提供的注解,這使得在編譯期,MapStruct就可以直接生成bean映射的代碼,相當(dāng)于代替我們寫了很多setter和getter。
          如我們?cè)诖a中定義了以下一個(gè)Mapper:

          @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);

          ????}

          }

          經(jīng)過代碼編譯后,會(huì)自動(dòng)生成一個(gè)PersonConverterImpl:

          @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;

          ????}

          }

          在運(yùn)行期,對(duì)于bean進(jìn)行映射的時(shí)候,就會(huì)直接調(diào)用PersonConverterImpl的dto2do方法,這樣就沒有什么特殊的事情要做了,只是在內(nèi)存中進(jìn)行set和get就可以了。
          所以,因?yàn)樵诰幾g期做了很多事情,所以MapStruct在運(yùn)行期的性能會(huì)很好,并且還有一個(gè)好處,那就是可以把問題的暴露提前到編譯期。
          使得如果代碼中字段映射有問題,那么應(yīng)用就會(huì)無法編譯,強(qiáng)制開發(fā)者要解決這個(gè)問題才行。



          總結(jié)
          本文介紹了一款Java中的字段映射工具類,MapStruct,他的用法比較簡單,并且功能非常完善,可以應(yīng)付各種情況的字段映射。
          并且因?yàn)樗蔷幾g期就會(huì)生成真正的映射代碼,使得運(yùn)行期的性能得到了大大的提升。

          強(qiáng)烈推薦,真的很香?。。?/span>



          往期推薦

          用了Dapper之后通篇還是SqlConnection,真的看不下去了


          實(shí)用!一鍵生成數(shù)據(jù)庫文檔,堪稱數(shù)據(jù)庫界的Swagger


          為什么阿里巴巴要求日期格式化時(shí)必須有使用y表示年,而不能用Y?


          本文由“壹伴編輯器”提供技術(shù)支
          ?

          直面Java第329期:哪個(gè)命令可以監(jiān)控虛擬機(jī)各種運(yùn)行狀態(tài)信息?

          深入并發(fā)第013期:拓展synchronized——鎖優(yōu)化


          如果你喜歡本文,

          請(qǐng)長按二維碼,關(guān)注?Hollis.

          轉(zhuǎn)發(fā)至朋友圈,是對(duì)我最大的支持。


          點(diǎn)個(gè)?在看?
          喜歡是一種感覺
          在看是一種支持
          ↘↘↘
          瀏覽 49
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产成人亚洲综合AV婷婷 | 日韩小视频在线观看 | seseseaaa | 色狠狠tv | 国产AV影视 |