自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

還在用BeanUtils拷貝對象?MapStruct才是王者!

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

[[428964]]

本文轉(zhuǎn)載自微信公眾號「阿Q說代碼」,作者阿Q 。轉(zhuǎn)載本文請聯(lián)系阿Q說代碼眾號。

前幾天,遠(yuǎn)在北京的小伙伴在群里拋出了“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)建(Maven、Gradle等)中使用,也可以在首選 IDE 中使用。它使用合理的默認(rèn)值,但在配置或?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. 引入依賴

  1. <dependency> 
  2.     <groupId>org.mapstruct</groupId> 
  3.     <artifactId>mapstruct-jdk8</artifactId> 
  4.     <version>1.3.0.Final</version> 
  5. </dependency> 
  6. //注解處理器,根據(jù)注解自動生成mapper的實現(xiàn) 
  7. <dependency> 
  8.     <groupId>org.mapstruct</groupId> 
  9.     <artifactId>mapstruct-processor</artifactId> 
  10.     <version>1.2.0.Final</version> 
  11. </dependency> 

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

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

  1. @NoArgsConstructor 
  2. @AllArgsConstructor 
  3. @Data 
  4. public class Car { 
  5.     private String make; 
  6.     private int numberOfSeats; 
  7.     private CarType type; 
  8.  
  9. @Data 
  10. @NoArgsConstructor 
  11. @AllArgsConstructor 
  12. public class CarDto { 
  13.     private String make; 
  14.     private int seatCount; 
  15.     private String type; 
  16.  

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

  1. @Mapper 
  2. public interface CarMapper { 
  3.   
  4.     CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); 
  5.  
  6.     @Mapping(source = "numberOfSeats", target = "seatCount"
  7.     CarDto carToCarDto(Car car);  
  8.     

解析分析:

  • @Mapper 將接口標(biāo)記為映射接口,并允許 MapStruct 處理器在編譯期間啟動。這里的 @Mapper 注解不是 mybatis 的注解,而是 org.mapstruct.Mapper 的;
  • 實際映射方法 carToCarDto() 期望源對象 Car 作為參數(shù),并返回目標(biāo)對象 CarDto ,方法名可以自由選擇;
  • 對于源對象和目標(biāo)對象中具有不同名稱的屬性,可以使用 @Mapping 注釋來配置名稱;
  • 對于源對象和目標(biāo)對象中具有不同類型的屬性,也可以使用 @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. 客戶端

  1. @Test 
  2. public void shouldMapCarToDto() { 
  3.  
  4.     Car car = new Car( "Morris", 5, CarType.SEDAN ); 
  5.     CarDto carDto = CarMapper.INSTANCE.carToCarDto( car ); 
  6.     System.out.println(carDto); 
  7.      

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

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

MapStruct 配置

@Mapper

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

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

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

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

1. 定義一個 CarVo.java 類

  1. @Data 
  2. @NoArgsConstructor 
  3. @AllArgsConstructor 
  4. public class CarVo { 
  5.  
  6.     private String make; 
  7.     private int seatCount; 
  8.     private boolean type; 

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

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

  1. public CarDto carVoToCarDto(CarVo carVo) { 
  2.  if (carVo == null) { 
  3.   return null
  4.  } else { 
  5.   CarDto carDto = new CarDto(); 
  6.   carDto.setMake(carVo.getMake()); 
  7.   carDto.setSeatCount(carVo.getSeatCount()); 
  8.   carDto.setType(String.valueOf(carVo.isType())); 
  9.   return carDto; 
  10.  } 

3.在 mapper 上增加 uses 屬性,并指定自定義的處理類,代碼如下: 

  1. @Mapper(uses = {BooleanStrFormat.class}) 
  2. public interface CarMapper { 
  3.     ...... 
  4.  
  5. /** 
  6. * 自定義的轉(zhuǎn)換類 
  7. */ 
  8. @Component 
  9. public class BooleanStrFormat { 
  10.     public String toStr(boolean type) { 
  11.         if(type){ 
  12.             return "Y"
  13.         }else
  14.             return "N"
  15.         } 
  16.     } 
  17.  
  18.     public boolean toBoolean(String type) { 
  19.         if (type.equals("Y")) { 
  20.             return true
  21.         } else { 
  22.             return false
  23.         } 
  24.     } 
  25.  
  26. /** 
  27. * 查看編譯后生成的實現(xiàn)類 
  28. */ 
  29. public CarDto carVoToCarDto(CarVo carVo) { 
  30.  if (carVo == null) { 
  31.   return null
  32.  } else { 
  33.   CarDto carDto = new CarDto(); 
  34.   carDto.setMake(carVo.getMake()); 
  35.   carDto.setSeatCount(carVo.getSeatCount()); 
  36.         //調(diào)用自定義的類中的方法 
  37.   carDto.setType(this.booleanStrFormat.toStr(carVo.isType())); 
  38.   return carDto; 
  39.  } 

4.客戶端代碼

  1. @Test 
  2. public void shouldMapCarVoToDto() { 
  3.  
  4.  CarVo carVo = new CarVo( "Morris", 5, false ); 
  5.  CarDto carDto = CarMapper.INSTANCE.carVoToCarDto( carVo ); 
  6.  
  7.  System.out.println(carDto); 

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

@Mapping

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

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

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

1. 在 mapper 中定義方法

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

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

  1. @Override 
  2. public CarDto carVoToDtoWithExpression(CarVo carVo) { 
  3.  if ( carVo == null ) { 
  4.   return null
  5.  } 
  6.  
  7.  CarDto carDto = new CarDto(); 
  8.  
  9.  carDto.setMake( carVo.getMake() ); 
  10.  carDto.setSeatCount( carVo.getSeatCount() ); 
  11.  
  12.  carDto.setType( new com.ittest.controller.BooleanStrFormat().toStr(carVo.isType()) ); 
  13.  
  14.  return carDto; 

3. 客戶端

  1. @Test 
  2. public void mapCarVoToDtoWithExpression() { 
  3.  
  4.  CarVo carVo = new CarVo( "Morris", 5, false ); 
  5.  CarDto carDto = CarMapper.INSTANCE.carVoToDtoWithExpression( carVo ); 
  6.  
  7.  System.out.println(carDto); 

運行結(jié)果:

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

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

@Mappings

可以配置多個 @Mapping,例如

  1. @Mappings({ 
  2.     @Mapping(source = "id", target = "carId"), 
  3.     @Mapping(source = "name", target = "carName"), 
  4.     @Mapping(source = "color", target = "carColor"
  5. }) 

@MappingTarget

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

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

  1. @NoArgsConstructor 
  2. @AllArgsConstructor 
  3. @Data 
  4. public class BMWCar { 
  5.     private String make; 
  6.     private int numberOfSeats; 
  7.     private CarType type; 
  8.  
  9.     private String color; 
  10.     private String price; 
  11.  

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

  1. // 更新方法 
  2. void updateBwmCar(Car car, @MappingTarget BMWCar bwmCar); 
  3.  
  4. // 實現(xiàn)類 
  5. public void updateBwmCar(Car car, BMWCar bwmCar) { 
  6.  if (car != null) { 
  7.   bwmCar.setMake(car.getMake()); 
  8.   bwmCar.setNumberOfSeats(car.getNumberOfSeats()); 
  9.   bwmCar.setType(car.getType()); 
  10.  } 

3. 客戶端代碼

  1. @Test 
  2. public void updateBwmCar() { 
  3.  Car car = new Car( "Morris", 5, CarType.SEDAN ); 
  4.  BMWCar bwmCar = new BMWCar("BWM", 5, CarType.SPORTS, "RED""50w"); 
  5.  System.out.println("更新前 car:"+car.toString()); 
  6.  System.out.println("更新前 BWMCar:"+bwmCar.toString()); 
  7.  
  8.  CarMapper.INSTANCE.updateBwmCar(car, bwmCar); 
  9.  
  10.  System.out.println("更新后 car:"+car.toString()); 
  11.  System.out.println("更新后 BWMCar:"+bwmCar.toString()); 

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

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

1. 準(zhǔn)備實體類 Benz4SMall.java 和 Mall4S.java

  1. @NoArgsConstructor 
  2. @AllArgsConstructor 
  3. @Data 
  4. public class Mall4S { 
  5.  
  6.     private String address; 
  7.  
  8.     private String mobile; 
  9.  
  10.  
  11. @Data 
  12. @NoArgsConstructor 
  13. @AllArgsConstructor 
  14. public class Benz4SMall { 
  15.  
  16.     private String address; 
  17.     private String mobile; 
  18.     private String make; 
  19.     private int numberOfSeats; 

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

  1. Benz4SMall mallCarToBenzMall(Car car, Mall4S mall4S); 
  2.  
  3. /** 
  4. * 實現(xiàn)類 
  5. */ 
  6. public Benz4SMall mallCarToBenzMall(Car car, Mall4S mall4S) { 
  7.  if (car == null && mall4S == null) { 
  8.   return null
  9.  } else { 
  10.   Benz4SMall benz4SMall = new Benz4SMall(); 
  11.   if (car != null) { 
  12.    benz4SMall.setMake(car.getMake()); 
  13.    benz4SMall.setNumberOfSeats(car.getNumberOfSeats()); 
  14.   } 
  15.  
  16.   if (mall4S != null) { 
  17.    benz4SMall.setAddress(mall4S.getAddress()); 
  18.    benz4SMall.setMobile(mall4S.getMobile()); 
  19.   } 
  20.  
  21.   return benz4SMall; 
  22.  } 

3. 客戶端

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

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

深拷貝與淺拷貝

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

假設(shè) B 復(fù)制了 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 可以理解為: 手工復(fù)制 > cglib > 反射 > Dozer。

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

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

 

責(zé)任編輯:武曉燕 來源: 阿Q說代碼
相關(guān)推薦

2022-09-02 15:11:18

開發(fā)工具

2025-04-25 10:28:40

2020-08-10 10:40:03

工具類MapStructJava

2020-08-10 14:30:09

BeanUtils工具類MapStruct

2012-07-19 10:03:32

2024-06-12 10:13:58

BeanUtils深拷貝代碼

2023-05-15 12:33:47

JavaPython編程語言

2024-11-12 16:28:34

2021-04-16 10:27:45

流程管理OA魔方網(wǎng)表

2024-04-11 09:17:51

ArraysJava安全

2022-09-02 08:17:40

MapStruct代碼工具

2024-06-03 00:00:06

高性能數(shù)據(jù)傳輸應(yīng)用程序

2022-05-31 09:57:36

編程語言Go語言Python

2020-03-04 14:05:35

戴爾

2021-01-03 17:14:16

ORMObjective S運行

2020-08-03 09:40:39

Python編程語言Instagram

2015-05-14 14:27:39

撥號上網(wǎng)

2022-10-27 12:15:20

DLP技術(shù)數(shù)據(jù)自主保護

2021-11-02 16:44:40

部署DevtoolsJRebel

2025-04-02 08:47:23

DOM文檔結(jié)構(gòu)API
點贊
收藏

51CTO技術(shù)棧公眾號