<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才是王者!【附源碼】

          共 9266字,需瀏覽 19分鐘

           ·

          2021-10-16 11:17

          前幾天,遠在北京的小伙伴在群里拋出了“MapStruct”的概念。對于只聞其名,未見其人的我來說,決定對其研究一番。本文我們就從 MapStruct 的概念出發(fā),通過具體的代碼示例來研究它的使用情況,最后與“市面上”的其它工具來做個對比!

          官方介紹

          首先我們打開 MapStruct 的官網(wǎng)地址,映入眼簾的就是下邊的三步曲:

          What is it?

          MapStruct 是一個代碼生成器,它基于約定優(yōu)先于配置的方法大大簡化了 JavaBean 類型之間映射的實現(xiàn)。生成的映射代碼使用普通方法調(diào)用,因此速度快、類型安全且易于理解。

          Why?

          多層應(yīng)用程序通常需要在不同的對象模型(例如實體和 DTO)之間進行映射。編寫這樣的映射代碼是一項乏味且容易出錯的任務(wù)。MapStruct 旨在通過盡可能自動化來簡化這項工作。

          與其他映射框架不同,MapStruct編譯時生成 bean 映射,這確保了高性能,允許快速的開發(fā)人員反饋和徹底的錯誤檢查。

          How?

          MapStruct 是插入 Java 編譯器的注釋處理器,可以在命令行構(gòu)建(MavenGradle等)中使用,也可以在首選 IDE 中使用。它使用合理的默認值,但在配置或?qū)崿F(xiàn)特殊行為時,用戶可以自定義實現(xiàn)。

          官網(wǎng)的解釋總是咬文嚼字,晦澀難懂的,看到這你只需要記住 MapStruct 是用來做實體類映射——實體類拷貝 的就可以了。

          源碼地址:https://github.com/mapstruct/mapstruct
          官網(wǎng)推薦的 Demo: https://github.com/mapstruct/mapstruct-examples

          簡單實現(xiàn)

          我們注意到官網(wǎng)中有涉及到簡單樣例的實現(xiàn),我們用2分鐘來分析一波:

          1. 引入依賴


          org.mapstruct
          mapstruct-jdk8
          1.3.0.Final

          //注解處理器,根據(jù)注解自動生成mapper的實現(xiàn)

          org.mapstruct
          mapstruct-processor
          1.2.0.Final

          我們在編譯時會報 java: No property named "numberOfSeats" exists in source parameter(s). Did you mean "null"? 錯誤,經(jīng)過查閱資料發(fā)現(xiàn) mapstruct-processorLombok 的版本需要統(tǒng)一一下:mapstruct-processor1.2.0.FinalLombok1.16.14

          2. 準備實體類 Car.java 和 數(shù)據(jù)傳輸類 CarDto.java

          @NoArgsConstructor
          @AllArgsConstructor
          @Data
          public?class?Car?{
          ????private?String?make;
          ????private?int?numberOfSeats;
          ????private?CarType?type;
          }

          @Data
          @NoArgsConstructor
          @AllArgsConstructor
          public?class?CarDto?{
          ????private?String?make;
          ????private?int?seatCount;
          ????private?String?type;

          }

          3. 創(chuàng)建映射器接口,里邊定義映射方法

          @Mapper
          public?interface?CarMapper?{
          ?
          ????CarMapper?INSTANCE?=?Mappers.getMapper(?CarMapper.class?);

          ????@Mapping(source?=?"numberOfSeats",?target?=?"seatCount")
          ????CarDto?carToCarDto(Car?car);?
          ???
          }

          解析分析:

          • @Mapper 將接口標記為映射接口,并允許 MapStruct 處理器在編譯期間啟動。這里的 @Mapper 注解不是 mybatis 的注解,而是 org.mapstruct.Mapper 的;
          • 實際映射方法 carToCarDto() ?期望源對象 Car 作為參數(shù),并返回目標對象 CarDto ,方法名可以自由選擇;
          • 對于源對象和目標對象中具有不同名稱的屬性,可以使用 @Mapping 注釋來配置名稱;
          • 對于源對象和目標對象中具有不同類型的屬性,也可以使用 @Mapping 注釋來進行轉(zhuǎn)換,比如:類型屬性將從枚舉類型轉(zhuǎn)換為字符串;
          • 一個接口中可以有多個映射方法,對于所有的這些方法,MapStruct 將生成一個實現(xiàn);
          • 該接口的實現(xiàn)實例可以從 Mappers 中獲得,接口聲明一個 INSTANCE,為客戶端提供對映射器實現(xiàn)的訪問。

          4. 實現(xiàn)類

          我們可以將代碼進行編譯,然后會發(fā)現(xiàn)在 target 文件中生成了 CarMapperImpl.class 文件:

          從代碼中可以看出 MapStruct 為我們自動生成了 set/get 代碼,并且對枚舉類進行了特殊處理。

          5. 客戶端

          @Test
          public?void?shouldMapCarToDto()?{

          ????Car?car?=?new?Car(?"Morris",?5,?CarType.SEDAN?);
          ????CarDto?carDto?=?CarMapper.INSTANCE.carToCarDto(?car?);
          ????System.out.println(carDto);
          ????
          }

          執(zhí)行結(jié)果:

          小結(jié): MapStruct 基于 mapper 接口,在編譯期動態(tài)生成 set/get 代碼的 class 文件 ,在運行時直接調(diào)用該 class 文件。

          MapStruct 配置

          @Mapper

          我們翻開上邊提到的 Mapper 注釋的源碼,該注釋的解釋是:將接口或抽象類標記為映射器,并通過 MapStruct 激活該類型實現(xiàn)的生成。我們找到其中的 componentModel 屬性,默認值為 default,它有四種值供我們選擇:

          • default:映射器不使用組件模型,實例通常通過 Mappers.getMapper(java.lang.Class)獲取;
          • cdi:生成的映射器是 application-scopedCDI bean,可以通過 @Inject 獲取;
          • spring:生成的映射器是 Spring bean,可以通過 @Autowired 獲取;
          • jsr330:生成的映射器被 @javax.inject.Named@Singleton 注釋,可以通過 @inject 獲取;

          上邊我們用的就是默認的方法,當然我們也可以用 @Autowired 來引入接口依賴,此處不再舉例,有興趣的小伙伴可以自己試試!

          另外我們可以看下 uses 屬性:可以通過定義其他類來完成字段轉(zhuǎn)換,接下來我們來個小例子演示一下:

          1. 定義一個 CarVo.java 類

          @Data
          @NoArgsConstructor
          @AllArgsConstructor
          public?class?CarVo?{

          ????private?String?make;
          ????private?int?seatCount;
          ????private?boolean?type;
          }

          2. 在 mapper 中定義一個 vo 轉(zhuǎn)為 dto 的方法 CarDto carVoToCarDto(CarVo carVo);

          當不加 uses 屬性時,查看編譯后生成的實現(xiàn)類

          public?CarDto?carVoToCarDto(CarVo?carVo)?{
          ?if?(carVo?==?null)?{
          ??return?null;
          ?}?else?{
          ??CarDto?carDto?=?new?CarDto();
          ??carDto.setMake(carVo.getMake());
          ??carDto.setSeatCount(carVo.getSeatCount());
          ??carDto.setType(String.valueOf(carVo.isType()));
          ??return?carDto;
          ?}
          }
          1. mapper 上增加 uses 屬性,并指定自定義的處理類,代碼如下:
          @Mapper(uses?=?{BooleanStrFormat.class})
          public?interface?CarMapper?
          {
          ????......
          }

          /**
          *?自定義的轉(zhuǎn)換類
          */

          @Component
          public?class?BooleanStrFormat?{
          ????public?String?toStr(boolean?type)?{
          ????????if(type){
          ????????????return?"Y";
          ????????}else{
          ????????????return?"N";
          ????????}
          ????}

          ????public?boolean?toBoolean(String?type)?{
          ????????if?(type.equals("Y"))?{
          ????????????return?true;
          ????????}?else?{
          ????????????return?false;
          ????????}
          ????}
          }

          /**
          *?查看編譯后生成的實現(xiàn)類
          */

          public?CarDto?carVoToCarDto(CarVo?carVo)?{
          ?if?(carVo?==?null)?{
          ??return?null;
          ?}?else?{
          ??CarDto?carDto?=?new?CarDto();
          ??carDto.setMake(carVo.getMake());
          ??carDto.setSeatCount(carVo.getSeatCount());
          ????????//調(diào)用自定義的類中的方法
          ??carDto.setType(this.booleanStrFormat.toStr(carVo.isType()));
          ??return?carDto;
          ?}
          }

          4.客戶端代碼

          @Test
          public?void?shouldMapCarVoToDto()?{

          ?CarVo?carVo?=?new?CarVo(?"Morris",?5,?false?);
          ?CarDto?carDto?=?CarMapper.INSTANCE.carVoToCarDto(?carVo?);

          ?System.out.println(carDto);
          }

          執(zhí)行結(jié)果:

          @Mapping

          @Mapping 可以用來配置一個 bean 屬性或枚舉常量的映射,默認是將具有相同名稱的屬性進行映射,當然也可以用 sourceexpression 或者 constant 屬性手動指定,接下來我們來分析下常用的屬性值。

          1. target:屬性的目標名稱,同一目標屬性不能映射多次。如果用于映射枚舉常量,則將給出常量成員的名稱,在這種情況下,源枚舉中的多個值可以映射到目標枚舉的相同值。
          2. source:屬性的源名稱,
          • 如果帶注釋的方法有多個源參數(shù),則屬性名稱必須使用參數(shù)名稱限定,例如“addressParam.city"
          • 當找不到匹配的屬性時,MapStruct 將查找匹配的參數(shù)名稱;
          • 當用于映射枚舉常量時,將給出常量成員的名稱;
          • 該屬性不能與 constantexpression 一起使用;
          1. dateFormat:通過 SimpleDateFormat 實現(xiàn) StringDate 日期之間相互轉(zhuǎn)換。
          2. numberFormat:通過 DecimalFormat 實現(xiàn) NumberString 的數(shù)值格式化。
          3. constant:設(shè)置指定目標屬性的常量字符串,當指定的目標屬性的類型為:primitiveboxed(例如 Long)時,MapStruct 檢查是否可以將該 primitive 作為有效的文本分配給 primitiveboxed 類型。如果可能,MapStruct 將分配為文字;如果不可能,MapStruct 將嘗試應(yīng)用用戶定義的映射方法。 另外,MapStruct 將常量作為字符串處理,將通過應(yīng)用匹配方法、類型轉(zhuǎn)換方法或內(nèi)置轉(zhuǎn)換來轉(zhuǎn)換該值。此屬性不能與 sourcedefaultValuedefaultExpressionexpression 一起使用。
          4. expression:是一個表達式,根據(jù)該表達式設(shè)置指定的目標屬性。他的屬性不能與 sourcedefaultValuedefaultExpressionconstant 一起使用。
          5. ignore: 忽略這個字段。

          我們用 expression 這個屬性來實現(xiàn)一下上邊用 uses 實現(xiàn)的案例:

          1. 在 mapper 中定義方法

          @Mapping(target?=?"type",?expression?=?"java(new?com.ittest.controller.BooleanStrFormat().toStr(carVo.isType()))")
          CarDto?carVoToDtoWithExpression(CarVo?carVo);

          2. 生成的實現(xiàn)類

          @Override
          public?CarDto?carVoToDtoWithExpression(CarVo?carVo)?{
          ?if?(?carVo?==?null?)?{
          ??return?null;
          ?}

          ?CarDto?carDto?=?new?CarDto();

          ?carDto.setMake(?carVo.getMake()?);
          ?carDto.setSeatCount(?carVo.getSeatCount()?);

          ?carDto.setType(?new?com.ittest.controller.BooleanStrFormat().toStr(carVo.isType())?);

          ?return?carDto;
          }

          3. 客戶端

          @Test
          public?void?mapCarVoToDtoWithExpression()?{

          ?CarVo?carVo?=?new?CarVo(?"Morris",?5,?false?);
          ?CarDto?carDto?=?CarMapper.INSTANCE.carVoToDtoWithExpression(?carVo?);

          ?System.out.println(carDto);
          }

          運行結(jié)果:

          至于其他的用法大家可以多多探索。

          重要提示:枚舉映射功能已被棄用,并被 ValueMapping 取代。它將在后續(xù)版本中刪除。

          @Mappings

          可以配置多個 @Mapping,例如

          @Mappings({
          ????@Mapping(source?=?"id",?target?=?"carId"),
          ????@Mapping(source?=?"name",?target?=?"carName"),
          ????@Mapping(source?=?"color",?target?=?"carColor")
          })

          @MappingTarget

          用于更新已有對象,還是用例子來說明吧:

          1. 創(chuàng)建 BMWCar.java 類

          @NoArgsConstructor
          @AllArgsConstructor
          @Data
          public?class?BMWCar?{
          ????private?String?make;
          ????private?int?numberOfSeats;
          ????private?CarType?type;

          ????private?String?color;
          ????private?String?price;

          }

          2. mapper 中創(chuàng)建更新方法,并查看實現(xiàn)類

          //?更新方法
          void?updateBwmCar(Car?car,?@MappingTarget?BMWCar?bwmCar);

          //?實現(xiàn)類
          public?void?updateBwmCar(Car?car,?BMWCar?bwmCar)?{
          ?if?(car?!=?null)?{
          ??bwmCar.setMake(car.getMake());
          ??bwmCar.setNumberOfSeats(car.getNumberOfSeats());
          ??bwmCar.setType(car.getType());
          ?}
          }

          3. 客戶端代碼

          @Test
          public?void?updateBwmCar()?{
          ?Car?car?=?new?Car(?"Morris",?5,?CarType.SEDAN?);
          ?BMWCar?bwmCar?=?new?BMWCar("BWM",?5,?CarType.SPORTS,?"RED",?"50w");
          ?System.out.println("更新前?car:"+car.toString());
          ?System.out.println("更新前?BWMCar:"+bwmCar.toString());

          ?CarMapper.INSTANCE.updateBwmCar(car,?bwmCar);

          ?System.out.println("更新后?car:"+car.toString());
          ?System.out.println("更新后?BWMCar:"+bwmCar.toString());
          }

          執(zhí)行結(jié)果:

          擴展:多個對象映射一個對象

          1. 準備實體類 Benz4SMall.javaMall4S.java

          @NoArgsConstructor
          @AllArgsConstructor
          @Data
          public?class?Mall4S?{

          ????private?String?address;

          ????private?String?mobile;

          }

          @Data
          @NoArgsConstructor
          @AllArgsConstructor
          public?class?Benz4SMall?{

          ????private?String?address;
          ????private?String?mobile;
          ????private?String?make;
          ????private?int?numberOfSeats;
          }

          2. mapper 創(chuàng)建轉(zhuǎn)換方法并查看生成的實現(xiàn)類

          Benz4SMall?mallCarToBenzMall(Car?car,?Mall4S?mall4S);

          /**
          *?實現(xiàn)類
          */

          public?Benz4SMall?mallCarToBenzMall(Car?car,?Mall4S?mall4S)?{
          ?if?(car?==?null?&&?mall4S?==?null)?{
          ??return?null;
          ?}?else?{
          ??Benz4SMall?benz4SMall?=?new?Benz4SMall();
          ??if?(car?!=?null)?{
          ???benz4SMall.setMake(car.getMake());
          ???benz4SMall.setNumberOfSeats(car.getNumberOfSeats());
          ??}

          ??if?(mall4S?!=?null)?{
          ???benz4SMall.setAddress(mall4S.getAddress());
          ???benz4SMall.setMobile(mall4S.getMobile());
          ??}

          ??return?benz4SMall;
          ?}
          }

          3. 客戶端

          @Test
          public?void?mallCarToBenzMall()?{
          ?Car?car?=?new?Car(?"Morris",?5,?CarType.SEDAN?);
          ?Mall4S?mall4S?=?new?Mall4S("北京市",?"135XXXX4503");
          ?Benz4SMall?benz4SMall?=?CarMapper.INSTANCE.mallCarToBenzMall(car,?mall4S);
          ?System.out.println(benz4SMall.toString());
          }

          執(zhí)行結(jié)果

          深拷貝與淺拷貝

          深拷貝和淺拷貝最根本的區(qū)別在于是否真正獲取一個對象的復制實體,而不是引用。

          假設(shè) B 復制了 A ,修改 A 的時候,看 B 是否發(fā)生變化:如果 B 跟著也變了,說明是淺拷貝,拿人手短!(修改堆內(nèi)存中的同一個值);如果 B 沒有改變,說明是深拷貝,自食其力!(修改堆內(nèi)存中的不同的值)

          MapStruct 中是創(chuàng)建新的對象,也就是深拷貝

          MapStruct 與其他 Copy 的對比

          我們在平時的項目中經(jīng)常會使用到拷貝的功能,今天我們就將他們做一下對比,直接拋出 ZhaoYingChao88 大佬的實驗結(jié)果:

          輸出結(jié)果:手動Copy >Mapstuct>= cglibCopy > springBeanUtils > apachePropertyUtils > apacheBeanUtils 可以理解為: 手工復制 > cglib > 反射 > Dozer

          根據(jù)測試結(jié)果,我們可以得出在速度方面,MapStruct 是最好的,執(zhí)行速度是 Apache BeanUtils 的10倍、Spring BeanUtils 的 4-5倍、和 BeanCopier 的速度差不多。

          總結(jié):在大數(shù)據(jù)量級的情況下,MapStructBeanCopier 都有著較高的性能優(yōu)勢,其中 MapStruct 尤為優(yōu)秀。如果你僅是在日常處理少量的對象時,選取哪個其實變得并不重要,但數(shù)據(jù)量大時建議還是使用 MapStructBeanCopier 的方式,提高接口性能。

          參考鏈接:https://blog.csdn.net/ZYC88888/article/details/109681423?spm=1001.2014.3001.5501

          回復“mapstruct”,即可獲取源碼呦!

          以上就是今天的全部內(nèi)容了,如果你有不同的意見或者更好的idea,歡迎聯(lián)系阿Q,添加阿Q可以加入技術(shù)交流群參與討論呦!

          文章風格多變,配圖通俗易懂,故事生動有趣。推薦幾篇文章,何不試著讀讀呢?


          覺得還不錯?記得一鍵四連呦??

          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产精品 亚洲无码 | 豆花视频www888 | 一级网站女人一级 | 操逼操逼操逼操逼操 | 操老女人骚逼 |