Mapstruct详细使用说明
前言
在使用分層或者分模塊化的項目中,我們可能定義各種各樣的O,例如:DO,VO,DTO等等。我們在進行這些對象之間的拷貝時,通過手動寫get/set方法進行屬性之間的賦值。因為他們之間的屬性大部分都是相同的,不僅浪費時間,并且還有大量重復代碼。所以,各種框架都添加的對象之間的拷貝的工具類。例如:
Spring自帶了BeanUtils
Apatch自帶的BeanUtils
Apatch自帶的PropertyUtils
mapstruct提供Mappers
本文主要介紹MapStruct的基礎知識、Mapstruct的優缺點、Mapctruct的拷貝示例、以及四種方法時間對比。Mapstuct介紹
1.mapstruct的官網地址:
https://mapstruct.org
2.mapstruct的文檔地址:
https://mapstruct.org/documentation/stable/reference/html/
3.mapstrcut的示例github地址
https://github.com/mapstruct/mapstruct-examples
4.mapstruct的介紹
mapstruct的作用:就像mapstruct官網所說的:mapsrtuct是一個用于簡化在JavaBean之間映射的代碼生成器工具,并且是基于約定高于配置的方式。
mapstruct的優缺點
下面看看mapstruct相比其他映射工具有什么優點
1.mapstruct的優點:
mapstruct是在代碼編譯的時候,生成其映射規則的代碼,所以會在編譯時暴露映射錯誤的代碼,讓錯誤提前暴露。
因為使用的是一些簡單方法:set/get或者Fluent方式,而非反射方式,所以可以更快的執行完成??梢酝ㄟ^04章節查看各個方法效率上對比
可以實現深拷貝,上面4種方法中只有mapstruct可以實現深拷貝。但是需要進行配置(使用@mapper(mappingControl=DeepClone.class)進行設置)。其他的都是淺拷貝。從而mapstruct實現修改新對象不會對老對象產生影響。
類型更加安全
可以進行自定義的映射??梢宰远x各種方法,完成屬性映射。例如:將兩個屬性數據合并成一個設置到目標屬性上
2.mapstruct的缺點
必須添加一個接口或者抽象類,才能實現映射。其他的三種方法都不用寫額外接口或者類。
Mapstruct的簡單示例使用
上面主要是介紹一下Mapstruct的基礎知識,下面看一個mapstruct的簡單示例,完成屬性之間的映射。
在示例中,我們使用mapstruct的1.4.2.Final版本,進行試驗的1.第一個原屬性數據 //第一個拷貝對象 public class CompareA {private String name;private Integer age;private LocalDateTime createTime;private double score;private Double totalScore;private Date updateTIme;private ChildCompare childCompare;private Long days;//省略其get/set方法,也可使用Lombok,以后講解lombok與mapstruct結合使用}3.子類
//這是CompareA和CompareB共有的對象 public class ChildCompare {private String childName;private long childAge;//省略其get/set方法, }上面是兩個實體對象,觀察這兩個實體可以看到有兩點不同:
CompareA中有一個createTime屬性,數據類型為LocalDateTime,CompareB也有一個createTime,但是屬性類型為String
CompareA有一個days屬性,而CompareB有一個day屬性
4.mapstruct接口(這是需要我們手動寫的)
@Mapper public interface CompareMapper {CompareMapper INSTANCE = Mappers.getMapper(CompareMapper.class);@Mapping(target = "day", source = "days")@Mapping(target = "createTime", source = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")@Mapping(target = "childCompare", source = "childCompare", mappingControl = DeepClone.class)CompareB mapper(CompareA compareA); }從上面的實現中,我們加入了三個注解:
第一個注解:完成屬性名不匹配的問題,將compareA中的days(source)賦值到compareB的day(target)
第二個注解:完成屬性類型不匹配的問題,將compareA中的createTime,賦值到compareB的createTime,這里進行日期的格式轉化,使用dateFormat設置格式化類型
第三個注解:完成深度拷貝,將compareA中的childCompare屬性進行深度拷貝,而不是指針復制,最重要的是最后一個參數:mappingControl = DeepClone.class
5.最后就會自動生成一個實現類:
@Generated(value = "org.mapstruct.ap.MappingProcessor" ) public class CompareMapperImpl implements CompareMapper {@Overridepublic CompareB mapper(CompareA compareA) {if ( compareA == null ) {return null;}CompareB compareB = new CompareB();compareB.setDay( compareA.getDays() );if ( compareA.getCreateTime() != null ) {compareB.setCreateTime( DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm:ss" ).format( compareA.getCreateTime() ) );}compareB.setChildCompare( childCompareToChildCompare( compareA.getChildCompare() ) );compareB.setName( compareA.getName() );compareB.setAge( compareA.getAge() );compareB.setScore( compareA.getScore() );compareB.setTotalScore( compareA.getTotalScore() );compareB.setUpdateTIme( compareA.getUpdateTIme() );return compareB;}protected ChildCompare childCompareToChildCompare(ChildCompare childCompare) {if ( childCompare == null ) {return null;}ChildCompare childCompare1 = new ChildCompare();childCompare1.setChildName( childCompare.getChildName() );childCompare1.setChildAge( childCompare.getChildAge() );return childCompare1;} }從上面的實現,我們可以看出,添加一個childCompareToChildCompare方法,來完成對象之間的深度拷貝。同樣的將days設置到了day屬性,等等。
6.最后,我們調用這個mapper方法,就可以完成對象之間的拷貝:如下:CompareMapper.INSTANCE.mapper(new CompareA());
對象映射之間的比較
public class CompareTest {int count = 1000000;/*** Spring中的BeanUtils的使用,最后使用了6700ms*/@Testpublic void springBeanUtils() {CompareA populate = populate();StopWatch stopWatch = new StopWatch();stopWatch.start("開始拷貝");for (int i = 0; i < count; i++) {CompareB compareB = new CompareB();BeanUtils.copyProperties(populate, compareB);}stopWatch.stop();System.out.println(stopWatch.getTotalTimeMillis());}/*** 這是使用mapstruct進行對象拷貝,使用時間54ms*/@Testpublic void mapstructBeanUtils() {CompareA populate = populate();StopWatch stopWatch = new StopWatch();stopWatch.start("開始拷貝");for (int i = 0; i < count; i++) {CompareB mapper = CompareMapper.INSTANCE.mapper(populate);}stopWatch.stop();System.out.println(stopWatch.getTotalTimeMillis());}/*** 這是Apache的BeanUtils工具,使用7086ms* @throws InvocationTargetException* @throws IllegalAccessException*/@Testpublic void commonBeanUtils() throws InvocationTargetException, IllegalAccessException {CompareA populate = populate();StopWatch stopWatch = new StopWatch();stopWatch.start("開始拷貝");for (int i = 0; i < count; i++) {CompareB compareB = new CompareB();org.apache.commons.beanutils.BeanUtils.copyProperties(compareB, populate);}stopWatch.stop();System.out.println(stopWatch.getTotalTimeMillis());}/*** 使用Apache中的propertyUtils靜態方法,使用3756ms* @throws IllegalAccessException* @throws NoSuchMethodException* @throws InvocationTargetException*/@Testpublic void commonPropertiesUtils() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {CompareA populate = populate();StopWatch stopWatch = new StopWatch();stopWatch.start("開始拷貝");for (int i = 0; i < count; i++) {CompareB compareB = new CompareB();PropertyUtils.copyProperties(compareB, populate);}stopWatch.stop();System.out.println(stopWatch.getTotalTimeMillis());}public CompareA populate() {CompareA compareA = new CompareA();compareA.setAge(10);compareA.setName("moxiao");compareA.setCreateTime(LocalDateTime.now());compareA.setDays(100L);compareA.setTotalScore(100.0);compareA.setScore(12.3);compareA.setUpdateTIme(new Date());ChildCompare childCompare = new ChildCompare();childCompare.setChildAge(200);childCompare.setChildName("moxiaoChild");compareA.setChildCompare(childCompare);return compareA;}}綜上所述,1百萬數據,mapstruct可以在50ms左右完成。
Mapstruct的屬性之間的映射規則
在屬性映射方面,mapstruct相比于其他映射工具有:
- 可以進行不同屬性名之間的映射,使用@Mapping注解的source和target
- 可以使用表達式進行設置,使用@Mapping注解中的expression
- 可以設置默認值,使用@Mapping注解中的constant,
- 可以進行不同類型之間的映射,并且更加安全
- 可以進行自定義映射方法
mapstruct的映射控制說明
2.1 映射控制的作用:
在源對象和目標對象之間進行映射時,哪種映射方法將被考慮。即:只有在有這個控制條件下,才會執行對應的映射方法。所有屬性之間的復制或者拷貝都將是依據以下映射控制來完成的。只有更好地理解這四種映射控制,才能按照我們的想法進行對象之間的拷貝。2.2 映射控制的種類:
mapstruct一共有四種映射控制:
MappingControl.Use.DIRECT
直接映射:直接將源屬性賦值到目標屬性上,如果是對象的話,就是指針復制,也就是淺拷貝。這種方法有一個前提:目標屬性是源屬性的父類或者相同類型。
MappingControl.Use.BUILT_IN_CONVERSION
內置映射:將使用mapstruct內置的映射方法。有哪些內置映射方法,例如:日期和字符串、原生類型和對應的包裝對象、字符串和枚舉類型等等。
MappingControl.Use.MAPPING_METHOD
映射方法:包含兩類:可以是自定義的映射方法,也可以是系統自動生成的映射方法。注意:自定義映射方法比系統自動生成的映射方法的優先級更高。例如:深度拷貝就是使用這個映射控制。系統自動生成的映射方法:一般都是對象之間的映射。
MappingControl.Use.COMPLEX_MAPPING
復合映射:是一種結合BUILT_IN_CONVERSION和MAPPING_METHOD的映射控制,來完成對象屬性之間的拷貝。當進行COMPLEX_MAPPING時:一共有三種形態:
2.3 映射控組組合
四種映射控制可以組合使用,即同時使用兩個及以上的映射控制,從而在這些映射控制中選擇一種方式使用。那這就涉及到映射控制之間優先級。2.4 mapstruct內置的映射控制組合
在mapstruct中一共有三種內置的映射控制組合,以及自定義的映射組合。@MappingControl注解類
該注解使用了全部的映射控制:DIRECT、MAPPING_METHOD、 BUILT_IN_CONVERSION、COMPLEX_MAPPING。這也這是:默認使用映射控制。@DeepClone注解類
該注解只使用了:MAPPING_METHOD映射,當我們使用這個注解時,將不會采用其他的三種方式來完成屬性之間的拷貝。@NoComplexMapping注解類
改注解使用了三種:DIRECT、MAPPING_METHOD、 BUILT_IN_CONVERSION,即將不會使用復合映射方法完成屬性之間的拷貝自定義的映射組合
我們可以聲明一個注解類,自定義只是用哪些映射控制,例如:只使用內置的映射方法
@Retention(value = RetentionPolicy.CLASS)
@MappingControl( MappingControl.Use.BUILT_IN_CONVERSION )
public @interface BuildInConversionControl {
}
2.5 映射控制的使用
mapstruct默認使用的是:@MappingControl注解類,即四種映射控制我們可以在@Mapper注解中的mappingControl屬性中設置,他的屬性值是一個注解類。在@Mapper注解中設置這個屬性:將會應用到這個類中的所有方法的屬性之間的拷貝我們也可以在@Mapping注解中的mappingControl屬性中設置。在@Mapping注解中設置這個屬性:將只會應用到指定屬性上。@Mapping的優先級比@Mapper的高,即優先采用@Mapping,如果沒有才會使用@Mapper進行屬性之間的映射。mapstruct的映射控制優先級
在有多個映射控制使用時,例如在使用:@MappingControl注解,mapstruct將會如何進行屬性之間的映射。這里就涉及到四種映射控制之間的優先級。
3.1 四種映射控制的優先級,從高到低
在有MAPPING_METHOD控制下,并且包含有一個自定義的映射方法。這個自定義方法必須滿足:輸入參數為源屬性父級或者相同類型,這個映射方法的返回值為目標屬性的子類或相同類型。必須都滿足這兩個條件:這樣在進行屬性拷貝時,才會使用自定義的映射方法。不然則跳過。
在有DIRECT控制下,并且目標屬性必須是源屬性的父類或者相同類型。功能:直接將源屬性的指針拷貝到目標屬性下。必須滿足這兩個條件,才會使用指針拷貝,不然則跳過。
如果有BUILT_IN_CONVERSION的控制下,在源屬性和目標屬性之間拷貝時,mapstruct就會根據源屬性類型和目標屬性類型找到對應的內置映射方法使用,沒有找到,就跳過。
如果有COMPLEX_MAPPING控制下,它又劃分成三種類型:
如果存在兩個自定義的方法,可以將源屬性轉化為目標屬性,即: target = method1( method2( source ) ),優先使用。
3.2 注意事項:
如果源屬性是8種基本類型或者包裝體,而目標屬性是對應的類型(不能是不對應的),則會直接拷貝。就算有就不會走上面的優先級。例如:源屬性為long,目標屬性為Long,不管配置的什么,都會直接賦值。mapstruct的映射控制的示例
源
public class MappingOrder {private String name;private Integer age;private LocalDateTime birthTime;private MapperOrderChild mapperOrderChild;private MappingOrderChildChild mapperOrderChild2;//省略其set/get方法}目標對象
public class MappingOrderVO {private String name;private Number age;private String birthString;private MapperOrderChild mapperOrderChild;private MapperOrderChild2 mapperOrderChild2;}映射類(必須自己單獨定義一個,mapstruct必須提供一個mapper接口或者抽象類)
@Mapper public interface MapperOrderMapper {@Mapping(source = "birthTime", target = "birthString", dateFormat = "yyyy-MM-dd HH:mm:ss")//這一行進行深度拷貝,注意最后一個參數很重要:mappingControl = DeepClone.class@Mapping(source = "mapperOrderChild2", target = "mapperOrderChild2", mappingControl = DeepClone.class)MappingOrderVO entityToVo(MappingOrder mappingOrder);}mapstruct自動生成的文件
public class MapperOrderMapperImpl implements MapperOrderMapper {@Overridepublic MappingOrderVO entityToVo(MappingOrder mappingOrder) {if ( mappingOrder == null ) {return null;}MappingOrderVO mappingOrderVO = new MappingOrderVO();//第一個屬性,因為沒有自定義方法從LocalDateTime轉化為String,將跳過,//不能直接映射,優先級第二條//mapstruct有一個從LocalDateTime轉化為String的內置方法,從而滿足第三條映射if ( mappingOrder.getBirthTime() != null ) {mappingOrderVO.setBirthString( DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm:ss" ).format( mappingOrder.getBirthTime() ) );}//我們在方法中使用DEEPCLONE,即MAPPING_METHOD控制//所以只有第一條優先級和第五條優先級,滿足。因為沒有自定義映射方法,從而跳過//所以Mapstruct使用第五條,自動生成一個映射方法mappingOrderVO.setMapperOrderChild2( mappingOrderChildChildToMapperOrderChild2( mappingOrder.getMapperOrderChild2() ) );//DIRECT優先級獲勝mappingOrderVO.setName( mappingOrder.getName() );//DIRECT優先級獲勝mappingOrderVO.setAge( mappingOrder.getAge() );//DIRECT優先級獲勝mappingOrderVO.setMapperOrderChild( mappingOrder.getMapperOrderChild() );return mappingOrderVO;}protected MapperOrderChild2 mappingOrderChildChildToMapperOrderChild2(MappingOrderChildChild mappingOrderChildChild) {if ( mappingOrderChildChild == null ) {return null;}MapperOrderChild2 mapperOrderChild2 = new MapperOrderChild2();mapperOrderChild2.setFirstName( mappingOrderChildChild.getFirstName() );mapperOrderChild2.setSecondName( mappingOrderChildChild.getSecondName() );return mapperOrderChild2;} }總結
以上是生活随笔為你收集整理的Mapstruct详细使用说明的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: struct1
- 下一篇: typedef struct 和 str