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

七種對象復(fù)制工具類,阿粉該 Pick 誰?

開發(fā) 開發(fā)工具
日常編程中,我們會經(jīng)常會碰到對象屬性復(fù)制的場景,就比如下面這樣一個常見的三層 MVC 架構(gòu)。

[[339151]]

本文轉(zhuǎn)載自微信公眾號「Java極客技術(shù)」,作者鴨血粉絲 。轉(zhuǎn)載本文請聯(lián)系Java極客技術(shù)公眾號。  

日常編程中,我們會經(jīng)常會碰到對象屬性復(fù)制的場景,就比如下面這樣一個常見的三層 MVC 架構(gòu)。

 

當(dāng)我們在上面的架構(gòu)下編程時,我們通常需要經(jīng)歷對象轉(zhuǎn)化,比如業(yè)務(wù)請求流程經(jīng)歷三層機(jī)構(gòu)后需要把 DTO 轉(zhuǎn)為DO然后在數(shù)據(jù)庫中保存。

當(dāng)需要從數(shù)據(jù)查詢數(shù)據(jù)頁面展示時,查詢數(shù)據(jù)經(jīng)過三層架構(gòu)將會從 DO 轉(zhuǎn)為 DTO,最后再轉(zhuǎn)為 VO,然后在頁面中展示。

當(dāng)業(yè)務(wù)簡單的時候,我們手寫代碼,通過 getter/setter復(fù)制對象屬性,十分簡單。但是一旦業(yè)務(wù)變的復(fù)雜,對象屬性變得很多,那么手寫代碼就會成為程序員的噩夢。

不但手寫十分繁瑣,非常耗時間,并且還可能容易出錯。

阿粉之前就經(jīng)歷過一個項目,一個對象中大概有四五十個字段屬性,那時候阿粉還剛?cè)腴T,什么都不太懂,寫了半天 getter/setter復(fù)制對象屬性。

話外音:一個對象屬性這么多,顯然是不太合理的,我們設(shè)計過程應(yīng)該將其拆分。

直到后來,阿粉了解到了對象屬性復(fù)制工具類,使用之后,發(fā)現(xiàn)是真香,再也不用手寫代碼。再后來,碰到越來越多工具類,雖然核心功能都是一樣的,但是還是存在很多差異。新手看到可能會一臉懵逼,不知道如何選擇。

所以阿粉今天這篇介紹一下市面上常用的工具類:

  • Apache BeanUtils
  • Spring BeanUtils
  • Cglib BeanCopier
  • Dozer
  • orika
  • MapStruct

工具類特性

在介紹這些工具類之前,我們來看下一個好用的屬性復(fù)制工具類,需要有哪些特性:

  • 基本屬性復(fù)制,這個是基本功能
  • 不同類型的屬性賦值,比如基本類型與其包裝類型等
  • 不同字段名屬性賦值,當(dāng)然字段名應(yīng)該盡量保持一致,但是實際業(yè)務(wù)中,由于不同開發(fā)人員,或者筆誤拼錯單詞,這些原因都可能導(dǎo)致會字段名不一致的情況
  • 淺拷貝/深拷貝,淺拷貝會引用同一對象,如果稍微不慎,同時改動對象,就會踩到意想不到的坑

下面我們開始介紹工具類。

畫外音:公號內(nèi)回復(fù)「20200822」獲取源碼

Apache BeanUtils

首先介紹是第一位應(yīng)該是 Java 領(lǐng)域?qū)傩詮?fù)制的最有名的工具類「Apache BeanUtils」,這個工具類想必很多人或多或少用過或則見過。

沒用過也沒關(guān)系,我們來展示這個類的用法,用法非常簡單。

首先我們引入依賴,這里使用最新版本:

  1. <dependency> 
  2.     <groupId>commons-beanutils</groupId> 
  3.     <artifactId>commons-beanutils</artifactId> 
  4.     <version>1.9.4</version> 
  5. </dependency> 

假設(shè)我們項目中有如下類:

 

此時我們需要完成 DTO 對象轉(zhuǎn)化到 DO 對象,我們只需要簡單調(diào)用BeanUtils#copyProperties 方法就可以完成對象屬性的復(fù)制。

  1. StudentDTO studentDTO = new StudentDTO(); 
  2. studentDTO.setName("阿粉"); 
  3. studentDTO.setAge(18); 
  4. studentDTO.setNo("6666"); 
  5.  
  6. List<String> subjects = new ArrayList<>(); 
  7. subjects.add("math"); 
  8. subjects.add("english"); 
  9. studentDTO.setSubjects(subjects); 
  10. studentDTO.setCourse(new Course("CS-1")); 
  11. studentDTO.setCreateDate("2020-08-08"); 
  12.  
  13. StudentDO studentDO = new StudentDO(); 
  14.  
  15. BeanUtils.copyProperties(studentDO, studentDTO); 

不過,上面的代碼如果你這么寫,我們會碰到第一個問題,BeanUtils 默認(rèn)不支持 String轉(zhuǎn)為 Date 類型。

 

為了解決這個問題,我們需要自己構(gòu)造一個 Converter 轉(zhuǎn)換類,然后使用 ConvertUtils注冊,使用方法如下:

  1. ConvertUtils.register(new Converter() { 
  2.     @SneakyThrows 
  3.     @Override 
  4.     public <DateDate convert(Class<Date> type, Object value) { 
  5.         if (value == null) { 
  6.             return null
  7.         } 
  8.         if (value instanceof String) { 
  9.             String str = (String) value; 
  10.             return (Date) DateUtils.parseDate(str, "yyyy-MM-dd"); 
  11.         } 
  12.         return null
  13.  
  14.  
  15.     } 
  16. }, Date.class); 

此時,我們觀察 studentDO與 studentDTO對象屬性值:

 

從上面的圖我們可以得出BeanUtils一些結(jié)論:

  • 普通字段名不一致的屬性無法被復(fù)制
  • 嵌套對象字段,將會與源對象使用同一對象,即使用淺拷貝
  • 類型不一致的字段,將會進(jìn)行默認(rèn)類型轉(zhuǎn)化。

雖然 BeanUtils 使用起來很方便,不過其底層源碼為了追求完美,加了過多的包裝,使用了很多反射,做了很多校驗,所以導(dǎo)致性能較差,所以并阿里巴巴開發(fā)手冊上強(qiáng)制規(guī)定避免使用 Apache BeanUtils。

image-20200818222213879

 

Spring BeanUtils

Spring 屬性復(fù)制工具類類名與 Apache 一樣,基本用法也差不多。我先來看下 SpringBeanUtils 基本用法。

同樣,我們先引入依賴,從名字我們可以看出,BeanUtils 位于 Spring-Beans 模塊,這里我們依然使用最新模塊。

  1. <dependency> 
  2.     <groupId>org.springframework</groupId> 
  3.     <artifactId>spring-beans</artifactId> 
  4.     <version>5.2.8.RELEASE</version> 
  5. </dependency> 

這里我們使用 DTO 與 DO 復(fù)用上面的例子,轉(zhuǎn)換代碼如下:

  1. // 省略上面賦值代碼,與上面一致 
  2. StudentDO studentDO = new StudentDO(); 
  3.  
  4. BeanUtils.copyProperties(studentDTO, studentDO); 

從用法可以看到,Spring BeanUtils 與 Apache 有一個最大的不同,兩者源對象與目標(biāo)對象參數(shù)位置不一樣,阿粉之前沒注意,用了 Spring 工具類,但是卻是按照 Apache 的用法使用。

此時對比studentDO與 studentDTO對象:

 

從上面的對比圖我們可以得到一些結(jié)論:

  • 字段名不一致,屬性無法復(fù)制
  • 類型不一致,屬性無法復(fù)制。但是注意,如果類型為基本類型以及基本類型的包裝類,這種可以轉(zhuǎn)化
  • 嵌套對象字段,將會與源對象使用同一對象,即使用淺拷貝

除了這個方法之外,Spring BeanUtils 還提供了一個重載方法:

  1. public static void copyProperties(Object source, Object target, String... ignoreProperties)  

使用這個方法,我們可以忽略某些不想被復(fù)制過去的屬性:

  1. BeanUtils.copyProperties(studentDTO, studentDO,"name"); 

這樣,name 屬性就不會被復(fù)制到 DO 對象中。

 

雖然 Spring BeanUtils 與 Apache BeanUtils 功能差不多,但是在性能上 Spring BeanUtils 還是完爆 Apache BeanUtils。主要原因還是在于 Spring 并沒有與 Apache 一樣使用反射做了過多校驗,另外 Spring BeanUtils 內(nèi)部使用了緩存,加快轉(zhuǎn)換的速度。

所以兩者選擇,還是推薦使用 Spring BeanUtils。

Cglib BeanCopier

上面兩個是阿粉日常工作經(jīng)常使用,而下面的這些都是阿粉最近才開始接觸的,比如 Cglib BeanCopier。這個使用方法,可能比上面兩個類稍微復(fù)雜一點,下面我們來看下具體用法:

首先我們引入 Cglib 依賴:

  1. <dependency> 
  2.     <groupId>cglib</groupId> 
  3.     <artifactId>cglib</artifactId> 
  4.     <version>3.3.0</version> 
  5. </dependency> 

畫外音:如果你工程內(nèi)還有 Spring-Core 的話,如果查找 BeanCopier 這個類,可以發(fā)現(xiàn)兩個不同的包的同名類。

一個屬于 Cglib,另一個屬于 Spring-Core。

其實 Spring-Core 內(nèi)BeanCopier 實際就是引入了 Cglib 中的類,這么做的目的是為包了保證 Spring 使用長度 Cglib 相關(guān)類的穩(wěn)定性,防止外部 Cglib 依賴不一致,導(dǎo)致 Spring 運(yùn)行異常。

轉(zhuǎn)換代碼如下:

  1. // 省略賦值語句 
  2. StudentDO studentDO = new StudentDO(); 
  3. BeanCopier beanCopier = BeanCopier.create(StudentDTO.class, StudentDO.class, false); 
  4. beanCopier.copy(studentDTO, studentDO, null); 

使用方法相比 BeanUtils, BeanCopier 稍微多了一步。 對比studentDO與 studentDTO對象:

 

從上面可以得到與 Spring Beanutils 基本一致的結(jié)論:

  • 字段名不一致,屬性無法復(fù)制
  • 類型不一致,屬性無法復(fù)制。不過有點不一樣,如果類型為基本類型/基本類型的包裝類型,這兩者無法被拷貝。
  • 嵌套對象字段,將會與源對象使用同一對象,即使用淺拷貝

上面我們使用 Beanutils,遇到這種字段名,類型不一致的這種情況,我們沒有什么好辦法,只能手寫硬編碼。

不過在 BeanCopier 下,我們可以引入轉(zhuǎn)換器,進(jìn)行類型轉(zhuǎn)換。

  1. // 注意最后一個屬性設(shè)置為 true 
  2. BeanCopier beanCopier = BeanCopier.create(StudentDTO.class, StudentDO.class, true); 
  3. // 自定義轉(zhuǎn)換器 
  4. beanCopier.copy(studentDTO, studentDO, new Converter() { 
  5.     @Override 
  6.     public Object convert(Object source, Class target, Object context) { 
  7.         if (source instanceof Integer) { 
  8.             Integer num = (Integer) source; 
  9.             return num.toString(); 
  10.         } 
  11.         return null
  12.     } 
  13. }); 

不過吐槽一下這個轉(zhuǎn)換器,一旦我們自己打開使用轉(zhuǎn)換器,所有屬性復(fù)制都需要我們自己來了。比如上面的例子中,我們只處理當(dāng)源對象字段類型為 Integer,這種情況,其他都沒處理。我們得到 DO 對象將會只有 name 屬性才能被復(fù)制。

 

Cglib BeanCopier 的原理與上面兩個 Beanutils 原理不太一樣,其主要使用 字節(jié)碼技術(shù)動態(tài)生成一個代理類,代理類實現(xiàn)get 和 set方法。生成代理類過程存在一定開銷,但是一旦生成,我們可以緩存起來重復(fù)使用,所有 Cglib 性能相比以上兩種 Beanutils 性能比較好。

Dozer

Dozer ,中文直譯為挖土機(jī) ,這是一個「重量級」屬性復(fù)制工具類,相比于上面介紹三個工具類,Dozer 具有很多強(qiáng)大的功能。

[[339153]]

官網(wǎng) logo

 

畫外音:重量級/輕量級其實只是一個相對的說法,由于 Dozer 相對 BeanUtils 這類工具類來說,擁有許多高級功能,所以相對來說這是一個重量級工具類。

阿粉剛碰到這個工具類,就被深深折服,真的太強(qiáng)大了,上面我們期望的功能,Dozer 都給你實現(xiàn)了。

下面我們來看下使用方法,首先我們引入 Dozer 依賴:

  1. <dependency> 
  2.   <groupId>net.sf.dozer</groupId> 
  3.   <artifactId>dozer</artifactId> 
  4.   <version>5.4.0</version> 
  5. </dependency> 

使用方法如下:

  1. // 省略屬性的代碼 
  2. DozerBeanMapper mapper = new DozerBeanMapper(); 
  3. StudentDO studentDO = 
  4.         mapper.map(studentDTO, StudentDO.class); 
  5. System.out.println(studentDO); 

Dozer 需要我們新建一個DozerBeanMapper,這個類作用等同與 BeanUtils,負(fù)責(zé)對象之間的映射,屬性復(fù)制。

畫外音:下面的代碼我們可以看到,生成 DozerBeanMapper實例需要加載配置文件,隨意生成代價比較高。在我們應(yīng)用程序中,應(yīng)該使用單例模式,重復(fù)使用DozerBeanMapper。

如果屬性都是一些簡單基本類型,那我們只要使用上面代碼,可以快速完成屬性復(fù)制。

不過很不幸,我們的代碼中有字符串與 Date 類型轉(zhuǎn)化,如果我們直接使用上面的代碼,程序運(yùn)行將會拋出異常。

 

所以這里我們要用到 Dozer 強(qiáng)大的配置功能,我們總共可以使用下面三種方式:

  • XML
  • API
  • 注解

其中,API 的方式比較繁瑣,目前大部分使用 XML 進(jìn)行,另外注解功能的是在 Dozer 5.3.2 之后增加的新功能,不過功能相較于 XML 來說較弱。

XML 使用方式

下面我們使用 XML 配置方式,配置 DTO 與 DO 關(guān)系,首先我們新建一個 dozer/dozer-mapping.xml 文件:

  1. <?xml version="1.0" encoding="UTF-8"?> 
  2. <mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  3.           xsi:schemaLocation="http://dozer.sourceforge.net 
  4.           http://dozer.sourceforge.net/schema/beanmapping.xsd"> 
  5.     <!-- 類級別的日期轉(zhuǎn)換,默認(rèn)使用這個格式轉(zhuǎn)換    --> 
  6.     <mapping date-format="yyyy-MM-dd HH:mm:ss"
  7.         <class-a>com.just.doone.example.domain.StudentDTO</class-a> 
  8.         <class-b>com.just.doone.example.domain.StudentDO</class-b> 
  9.         <!-- 在下面指定字段名不一致的映射關(guān)系       --> 
  10.         <field> 
  11.             <a>no</a> 
  12.             <b>number</b> 
  13.         </field> 
  14.  
  15.         <field> 
  16.             <!-- 字段級別的日期轉(zhuǎn)換,將會覆蓋字段上的轉(zhuǎn)換            --> 
  17.             <a date-format="yy-MM-dd">createDate</a> 
  18.             <b>createDate</b> 
  19.         </field> 
  20.     </mapping> 
  21. </mappings> 

然后修改我們的 Java 代碼,增加讀取 Dozer 的配置文件:

  1. DozerBeanMapper mapper = new DozerBeanMapper(); 
  2. List<String> mappingFiles = new ArrayList<>(); 
  3. // 讀取配置文件 
  4. mappingFiles.add("dozer/dozer-mapping.xml"); 
  5. mapper.setMappingFiles(mappingFiles); 
  6. StudentDO studentDO = mapper.map(studentDTO, StudentDO.class); 
  7. System.out.println(studentDO); 

運(yùn)行之后,對比studentDO與 studentDTO對象:

 

從上面的圖我們可以發(fā)現(xiàn):

  • 類型不一致的字段,屬性被復(fù)制
  • DO 與 DTO 對象字段不是同一個對象,也就是深拷貝
  • 通過配置字段名的映射關(guān)系,不一樣字段的屬性也被復(fù)制

除了上述這些相對簡單的屬性以外,Dozer 還支持很多額外的功能,比如枚舉屬性復(fù)制,Map 等集合屬性復(fù)制等。

 

有些小伙伴剛看到 Dozer 的用法,可能覺得這個工具類比較繁瑣,不像 BeanUtils 工具類一樣一行代碼就可以解。

其實 Dozer 可以很好跟 Spring 框架整合,我們可以在 Spring 配置文件提前配置,后續(xù)我們只要引用 Dozer 的相應(yīng)的 Bean ,使用方式也是一行代碼。

Dozer 與 Spring 整合,我們可以使用其 DozerBeanMapperFactoryBean,配置如下:

  1. <bean class="org.dozer.spring.DozerBeanMapperFactoryBean"
  2.      <property name="mappingFiles"  
  3.                value="classpath*:/*mapping.xml"/> 
  4.    <!--自定義轉(zhuǎn)換器--> 
  5.      <property name="customConverters"
  6.          <list> 
  7.              <bean class= 
  8.                    "org.dozer.converters.CustomConverter"/>       
  9.          </list> 
  10.      </property> 
  11.  </bean> 

DozerBeanMapperFactoryBean支持設(shè)置屬性比較多,可以自定義設(shè)置類型轉(zhuǎn)換,還可以設(shè)置其他屬性。

另外還有一種簡單的方法,我們可以在 XML 中配置 DozerBeanMapper:

  1. <bean id="org.dozer.Mapper" class="org.dozer.DozerBeanMapper"
  2.         <property name="mappingFiles"
  3.             <list> 
  4.                 <value>dozer/dozer-Mapperpping.xml</value> 
  5.             </list> 
  6.         </property> 
  7.     </bean> 

Spring 配置完成之后,我們在代碼中可以直接注入:

  1. @Autowired 
  2. Mapper mapper; 
  3.  
  4. public void objMapping(StudentDTO studentDTO) { 
  5. // 直接使用 
  6. StudentDO studentDO = 
  7. mapper.map(studentDTO, StudentDO.class); 

注解方式

Dozer 注解方式相比 XML 配置來說功能很弱,只能完成字段名不一致的映射。

上面的代碼中,我們可以在 DTO 的 no 字段上使用 @Mapping 注解,這樣我們在使用 Dozer 完成轉(zhuǎn)換時,該字段屬性將會被復(fù)制。

  1. @Data 
  2. public class StudentDTO { 
  3.  
  4.     private String name
  5.  
  6.     private Integer age; 
  7.     @Mapping("number"
  8.     private String no
  9.  
  10.     private List<String> subjects; 
  11.  
  12.     private Course course; 
  13.     private String createDate; 

雖然目前注解功能有點薄弱,不過后看版本官方可能增加新的注解功能,另外 XML 與注解可以一起使用。

最后 Dozer 底層本質(zhì)上還是使用了反射完成屬性的復(fù)制,所以執(zhí)行速度并不是那么理想。

orika

orika也是一個跟 Dozer 類似的重量級屬性復(fù)制工具類,也提供諸如 Dozer 類似的功能。但是 orika 無需使用繁瑣 XML 配置,它自身提供一套非常簡潔的 API 用法,非常容易上手。

首先我們引入其最新的依賴:

  1. <dependency> 
  2.     <groupId>ma.glasnost.orika</groupId> 
  3.     <artifactId>orika-core</artifactId> 
  4.     <version>1.5.4</version> 
  5. </dependency> 

基本使用方法如下:

  1. // 省略其他設(shè)值代碼 
  2.  
  3. // 這里先不要設(shè)值時間 
  4. // studentDTO.setCreateDate("2020-08-08"); 
  5.  
  6. MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); 
  7. MapperFacade mapper = mapperFactory.getMapperFacade(); 
  8. StudentDO studentDO = mapper.map(studentDTO, StudentDO.class); 

這里我們引入兩個類 MapperFactory 與 MapperFacade,其中 MapperFactory 可以用于字段映射,配置轉(zhuǎn)換器等,而 MapperFacade 的作用就與 Beanutils 一樣,用于負(fù)責(zé)對象的之間的映射。

上面的代碼中,我們故意注釋了 DTO 對象中的 createDate 時間屬性的設(shè)值,這是因為默認(rèn)情況下如果沒有單獨設(shè)置時間類型的轉(zhuǎn)換器,上面的代碼將會拋錯。

另外,上面的代碼中,對于字段名不一致的屬性,是不會復(fù)制的,所以我們需要單獨設(shè)置。

下面我們就設(shè)置一個時間轉(zhuǎn)換器,并且指定一下字段名:

  1. MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); 
  2. ConverterFactory converterFactory = mapperFactory.getConverterFactory(); 
  3. converterFactory.registerConverter(new DateToStringConverter("yyyy-MM-dd")); 
  4. mapperFactory.classMap(StudentDTO.class, StudentDO.class) 
  5.         .field("no""number"
  6.       // 一定要調(diào)用下 byDefault 
  7.         .byDefault() 
  8.         .register(); 
  9. MapperFacade mapper = mapperFactory.getMapperFacade(); 
  10. StudentDO studentDO = mapper.map(studentDTO, StudentDO.class); 

上面的代碼中,首先我們需要在 ConverterFactory 注冊一個時間類型的轉(zhuǎn)換器,其次我們還需要再 MapperFactory 指定不同字段名的之間的映射關(guān)系。

這里我們要注意,在我們使用 classMap 之后,如果想要相同字段名屬性默認(rèn)被復(fù)制,那么一定調(diào)用 byDefault方法。

簡單對比一下 DTO 與 DO 對象:

 

上圖可以發(fā)現(xiàn) orika 的一些特性:

  • 默認(rèn)支持類型不一致(基本類型/包裝類型)轉(zhuǎn)換
  • 支持深拷貝
  • 指定不同字段名映射關(guān)系,屬性可以被成功復(fù)制。

另外 orika 還支持集合映射:

  1. MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); 
  2. List<Person> persons = new ArrayList<>(); 
  3. List<PersonDto> personDtos = mapperFactory.getMapperFacade().mapAsList(persons, PersonDto.class); 

最后聊下 orika 實現(xiàn)原理,orika 與 dozer 底層原理不太一樣,底層其使用了 javassist 生成字段屬性的映射的字節(jié)碼,然后直接動態(tài)加載執(zhí)行字節(jié)碼文件,相比于 Dozer 的這種使用反射原來的工具類,速度上會快很多。

 

MapStruct

不知不覺,一口氣已經(jīng)寫了 5 個屬性復(fù)制工具類,小伙伴都看到這里,那就不要放棄了,堅持看完,下面將介紹一個與上面這些都不太一樣的工具類「MapStruct」。

上面介紹的這些工具類,不管使用反射,還是使用字節(jié)碼技術(shù),這些都需要在代碼運(yùn)行期間動態(tài)執(zhí)行,所以相對于手寫硬編碼這種方式,上面這些工具類執(zhí)行速度都會慢很多。

那有沒有一個工具類的運(yùn)行速度與硬編碼這種方式差不多那?

這就要介紹 MapStruct 這個工具類,這個工具類之所以運(yùn)行速度與硬編碼差不多,這是因為他在編譯期間就生成了 Java Bean 屬性復(fù)制的代碼,運(yùn)行期間就無需使用反射或者字節(jié)碼技術(shù),所以確保了高性能。

另外,由于編譯期間就生成了代碼,所以如果有任何問題,編譯期間就可以提前暴露,這對于開發(fā)人員來講就可以提前解決問題,而不用等到代碼應(yīng)用上線了,運(yùn)行之后才發(fā)現(xiàn)錯誤。

下面我們來看下,怎么使用這個工具類,首先我們先引入這個依賴:

  1. <dependency> 
  2.     <groupId>org.mapstruct</groupId> 
  3.     <artifactId>mapstruct</artifactId> 
  4.     <version>1.3.1.Final</version> 
  5. </dependency> 

其次,由于 MapStruct 需要在編譯器期間生成代碼,所以我們需要 maven-compiler-plugin插件中配置:

  1. <plugin> 
  2.     <groupId>org.apache.maven.plugins</groupId> 
  3.     <artifactId>maven-compiler-plugin</artifactId> 
  4.     <version>3.8.1</version> 
  5.     <configuration> 
  6.         <source>1.8</source> <!-- depending on your project --> 
  7.         <target>1.8</target> <!-- depending on your project --> 
  8.         <annotationProcessorPaths> 
  9.             <path> 
  10.                 <groupId>org.mapstruct</groupId> 
  11.                 <artifactId>mapstruct-processor</artifactId> 
  12.                 <version>1.3.1.Final</version> 
  13.             </path> 
  14.             <!-- other annotation processors --> 
  15.         </annotationProcessorPaths> 
  16.     </configuration> 
  17. </plugin> 

接下來我們需要定義映射接口,代碼如下:

  1. @Mapper 
  2. public interface StudentMapper { 
  3.  
  4.     StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class); 
  5.    
  6.     @Mapping(source = "no", target = "number"
  7.     @Mapping(source = "createDate", target = "createDate", dateFormat = "yyyy-MM-dd"
  8.     StudentDO dtoToDo(StudentDTO studentDTO); 

我們需要使用 MapStruct 注解 @Mapper 定義一個轉(zhuǎn)換接口,這樣定義之后,StudentMapper 的功能就與 BeanUtils 等工具類一樣了。

其次,由于我們 DTO 與 DO 對象中存在字段名不一致的情況,所以我們還在在轉(zhuǎn)換方法上使用 @Mapping 注解指定字段映射。另外我們 createDate 字段類型不一致,這里我們還需要指定時間格式化類型。

上面定義完成之后,我們就可以直接使用 StudentMapper 一行代碼搞定對象轉(zhuǎn)換。

  1. // 忽略其他代碼 
  2. StudentDO studentDO = StudentMapper.INSTANCE.dtoToDo(studentDTO); 

如果我們對象使用 Lombok 的話,使用 @Mapping指定不同字段名,編譯期間可能會拋出如下的錯誤:

 

這個原因主要是因為 Lombok 也需要編譯期間自動生成代碼,這就可能導(dǎo)致兩者沖突,當(dāng) MapStruct 生成代碼時,還不存在 Lombok 生成的代碼。

解決辦法可以在 maven-compiler-plugin插件配置中加入 Lombok,如下:

  1. <plugin> 
  2.     <groupId>org.apache.maven.plugins</groupId> 
  3.     <artifactId>maven-compiler-plugin</artifactId> 
  4.     <version>3.8.1</version> 
  5.     <configuration> 
  6.         <source>1.8</source> <!-- depending on your project --> 
  7.         <target>1.8</target> <!-- depending on your project --> 
  8.         <annotationProcessorPaths> 
  9.             <path> 
  10.                 <groupId>org.mapstruct</groupId> 
  11.                 <artifactId>mapstruct-processor</artifactId> 
  12.                 <version>1.3.1.Final</version> 
  13.             </path> 
  14.             <path> 
  15.                 <groupId>org.projectlombok</groupId> 
  16.                 <artifactId>lombok</artifactId> 
  17.                 <version>1.18.12</version> 
  18.             </path> 
  19.             <!-- other annotation processors --> 
  20.         </annotationProcessorPaths> 
  21.     </configuration> 
  22. </plugin> 

輸出 DO 與 DTO 如下:

 

從上圖中我們可以得到一些結(jié)論:

  • 部分類型不一致,可以自動轉(zhuǎn)換,比如
    • 基本類型與包裝類型
    • 基本類型的包裝類型與 String

深拷貝

上面介紹的例子介紹一些簡單字段映射,如果小伙伴在工作總共還碰到其他的場景,可以先查看一下這個工程,查看一下有沒有結(jié)局解決辦法:https://github.com/mapstruct/mapstruct-examples

上面我們已經(jīng)知道 MapStruct 在編譯期間就生成了代碼,下面我們來看下自動生成代碼:

  1. public class StudentMapperImpl implements StudentMapper { 
  2.     public StudentMapperImpl() { 
  3.     } 
  4.  
  5.     public StudentDO dtoToDo(StudentDTO studentDTO) { 
  6.         if (studentDTO == null) { 
  7.             return null
  8.         } else { 
  9.             StudentDO studentDO = new StudentDO(); 
  10.             studentDO.setNumber(studentDTO.getNo()); 
  11.  
  12.             try { 
  13.                 if (studentDTO.getCreateDate() != null) { 
  14.                     studentDO.setCreateDate((new SimpleDateFormat("yyyy-MM-dd")).parse(studentDTO.getCreateDate())); 
  15.                 } 
  16.             } catch (ParseException var4) { 
  17.                 throw new RuntimeException(var4); 
  18.             } 
  19.  
  20.             studentDO.setName(studentDTO.getName()); 
  21.             if (studentDTO.getAge() != null) { 
  22.                 studentDO.setAge(String.valueOf(studentDTO.getAge())); 
  23.             } 
  24.  
  25.             List<String> list = studentDTO.getSubjects(); 
  26.             if (list != null) { 
  27.                 studentDO.setSubjects(new ArrayList(list)); 
  28.             } 
  29.  
  30.             studentDO.setCourse(studentDTO.getCourse()); 
  31.             return studentDO; 
  32.         } 
  33.     } 

從生成的代碼來看,里面并沒有什么黑魔法,MapStruct 自動生成了一個實現(xiàn)類 StudentMapperImpl,里面實現(xiàn)了 dtoToDo,方法里面調(diào)用 getter/setter設(shè)值。

從這個可以看出,MapStruct 作用就相當(dāng)于幫我們手寫getter/setter設(shè)值,所以它的性能會很好。

總結(jié)

看文這篇文章,我們一共學(xué)習(xí)了 7 個屬性復(fù)制工具類,這么多工具類我們該如何選擇那?阿粉講講自己的一些見解:

第一,首先我們直接拋棄 Apache Beanutils ,這個不用說了,阿里巴巴規(guī)范都這樣定了,我們就不要使用好了。

第二,當(dāng)然是看工具類的性能,這些工具類的性能,網(wǎng)上文章介紹的比較多,阿粉就復(fù)制過來,大家可以比較一下。

來自:https://www.hollischuang.com/archives/5337

 

 

來自:https://www.baeldung.com/java-performance-mapping-frameworks

 

可以看到 MapStruct 的性能可以說還是相當(dāng)優(yōu)秀。那么如果你的業(yè)務(wù)對于性能,響應(yīng)等要求比較高,或者你的業(yè)務(wù)存在大數(shù)據(jù)量導(dǎo)入/導(dǎo)出的場景,而這個代碼存在對象轉(zhuǎn)化,那就切勿使用 Apache Beanutils, Dozer 這兩個工具類。

第三,其實很大一部分應(yīng)用是沒有很高的性能的要求,只要工具類能提供足夠的便利,就可以接受。如果你的業(yè)務(wù)中沒有很復(fù)雜的的需求,那么直接使用 Spring Beanutils 就好了,畢竟 Spring 的包大部分應(yīng)用都在使用,我們都無需導(dǎo)入其他包了。

那么如果業(yè)務(wù)存在不同類型,不同的字段名,那么可以考慮使用 orika 等這種重量級工具類。

好了,今天的文章就到這里為止了~

參考

https://www.cnkirito.moe/orika/

https://www.hollischuang.com/archives/5337

http://dozer.sourceforge.net/documentation/usage.html

http://orika-mapper.github.io/orika-docs/faq.html

 

https://github.com/mapstruct/mapstruct-examples

 

責(zé)任編輯:武曉燕 來源: Java極客技術(shù)
相關(guān)推薦

2017-06-14 16:44:15

JavaScript原型模式對象

2010-03-15 15:55:00

Python開發(fā)工具

2023-08-27 18:49:06

2020-03-09 10:21:12

Java集合類 Guava

2010-06-08 09:39:40

UML圖

2010-10-15 10:02:01

Mysql表類型

2019-05-30 08:00:00

2018-04-27 09:00:00

代碼合并工具開源

2015-11-24 09:33:55

2010-06-08 09:49:45

UML元件

2011-03-14 10:46:03

2023-12-22 14:27:30

2025-01-21 08:00:00

限流微服務(wù)算法

2019-10-29 06:30:31

告警疲勞網(wǎng)絡(luò)安全安全風(fēng)險

2022-05-10 08:08:01

find命令Linux

2020-01-14 08:00:00

.NET緩存編程語言

2018-07-17 09:00:00

初創(chuàng)企業(yè)任務(wù)管理工具nTask

2019-04-17 09:00:00

DevOps基礎(chǔ)架構(gòu)代碼工具

2021-03-09 09:00:00

Python開發(fā)工具

2009-12-23 17:10:26

點贊
收藏

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