1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        還在用BeanUtils拷貝對(duì)象?MapStruct才是王者!【附源碼】

        共 9266字,需瀏覽 19分鐘

         ·

        2021-10-16 11:17

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

        官方介紹

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

        What is it?

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

        Why?

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

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

        How?

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

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

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

        簡(jiǎn)單實(shí)現(xiàn)

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

        1. 引入依賴


        org.mapstruct
        mapstruct-jdk8
        1.3.0.Final

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

        org.mapstruct
        mapstruct-processor
        1.2.0.Final

        我們?cè)诰幾g時(shí)會(huì)報(bào) java: No property named "numberOfSeats" exists in source parameter(s). Did you mean "null"? 錯(cuò)誤,經(jīng)過查閱資料發(fā)現(xiàn) mapstruct-processorLombok 的版本需要統(tǒng)一一下:mapstruct-processor1.2.0.Final , Lombok1.16.14。

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

        4. 實(shí)現(xiàn)類

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

        從代碼中可以看出 MapStruct 為我們自動(dòng)生成了 set/get 代碼,并且對(duì)枚舉類進(jìn)行了特殊處理。

        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 接口,在編譯期動(dòng)態(tài)生成 set/get 代碼的 class 文件 ,在運(yùn)行時(shí)直接調(diào)用該 class 文件。

        MapStruct 配置

        @Mapper

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

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

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

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

        1. 定義一個(gè) CarVo.java 類

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

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

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

        當(dāng)不加 uses 屬性時(shí),查看編譯后生成的實(shí)現(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;
        ????????}
        ????}
        }

        /**
        *?查看編譯后生成的實(shí)現(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 可以用來配置一個(gè) bean 屬性或枚舉常量的映射,默認(rèn)是將具有相同名稱的屬性進(jìn)行映射,當(dāng)然也可以用 source、expression 或者 constant 屬性手動(dòng)指定,接下來我們來分析下常用的屬性值。

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

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

        1. 在 mapper 中定義方法

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

        2. 生成的實(shí)現(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);
        }

        運(yùn)行結(jié)果:

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

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

        @Mappings

        可以配置多個(gè) @Mapping,例如

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

        @MappingTarget

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

        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)建更新方法,并查看實(shí)現(xiàn)類

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

        //?實(shí)現(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é)果:

        擴(kuò)展:多個(gè)對(duì)象映射一個(gè)對(duì)象

        1. 準(zhǔn)備實(shí)體類 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)換方法并查看生成的實(shí)現(xiàn)類

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

        /**
        *?實(shí)現(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ū)別在于是否真正獲取一個(gè)對(duì)象的復(fù)制實(shí)體,而不是引用。

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

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

        MapStruct 與其他 Copy 的對(duì)比

        我們?cè)谄綍r(shí)的項(xiàng)目中經(jīng)常會(huì)使用到拷貝的功能,今天我們就將他們做一下對(duì)比,直接拋出 ZhaoYingChao88 大佬的實(shí)驗(yàn)結(jié)果:

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

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

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

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

        回復(fù)“mapstruct”,即可獲取源碼呦!

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

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


        覺得還不錯(cuò)?記得一鍵四連呦??

        瀏覽 39
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            新天堂在线 | 亚洲国产中文字幕 | 边做奶水边喷h高h前夫 | 日韩欧美国产91 | 国产高清在线精品一区二区三区 | 一级毛片视频免费软件 | 成人无码Cosplay福利H视频 | 91热这里只有精品 | 老师让我她我爽了电影 | 国产对白叫床清晰在线播放 |