<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屬性轉(zhuǎn)換工具

          共 4726字,需瀏覽 10分鐘

           ·

          2021-05-14 20:31


          來源:https://blog.csdn.net/w605283073

          1. 背景

          之前在專欄中講過“不推薦使用屬性拷貝工具”,推薦直接定義轉(zhuǎn)換類和方法使用 IDEA 插件自動填充 get / set 函數(shù)。

          不推薦的主要理由是:

          • 有些屬性拷貝工具性能有點差

          • 有些屬性拷貝工具有“BUG”

          • 使用屬性拷貝工具容易存在一些隱患(后面例子會講到)

          2. 示例

          首先公司內(nèi)部就遇到過 commons 包的 BeanUtils 進(jìn)行屬性拷貝性能較差的真實案例,然后該同事?lián)Q成了 Spring 的 BeanUtils 性能好了很多,感興趣大家可以使用性能測試框架或者基準(zhǔn)測試框架去對比,這里就不對比了。

          接下來我們看 Spring 的 BeanUtils 的屬性拷貝會存在啥問題:

          import lombok.Data;
          import java.util.List;
          @Datapublic class A { private String name;
          private List<Integer> ids;}


          @Datapublic class B {    private String name;
          private List<String> ids;}


          import org.springframework.beans.BeanUtils;
          import java.util.Arrays;
          public class BeanUtilDemo { public static void main(String[] args) { A first = new A(); first.setName("demo"); first.setIds(Arrays.asList(1, 2, 3));
          B second = new B(); BeanUtils.copyProperties(first, second); for (String each : second.getIds()) {// 類型轉(zhuǎn)換異常 System.out.println(each); } }}

          大家運(yùn)行上述示例時,會發(fā)生類型轉(zhuǎn)換異常。

          打斷點可以看到,屬性拷貝之后 B 類型的 second 對象中 ids 仍然為 Integer 類型:

          如果不轉(zhuǎn)換為字符串,直接進(jìn)行打印,并不會報錯。


          使用CGlib 在不定義Converter 的情況下也會遇到類似問題:

          import org.easymock.cglib.beans.BeanCopier;
          import java.util.Arrays;
          public class BeanUtilDemo { public static void main(String[] args) { A first = new A(); first.setName("demo"); first.setIds(Arrays.asList(1, 2, 3));
          B second = new B(); final BeanCopier beanCopier = BeanCopier.create(A.class, B.class, false); beanCopier.copy(first,second,null);
          for (String each : second.getIds()) {// 類型轉(zhuǎn)換異常 System.out.println(each); } }}

          同樣,問題在運(yùn)行時才暴露出來。


          接下來我們看下 mapstruct:

          import org.mapstruct.Mapper;import org.mapstruct.factory.Mappers;
          @Mapperpublic interface Converter { Converter INSTANCE = Mappers.getMapper(Converter.class);
          B aToB(A car);}


          import java.util.Arrays;
          public class BeanUtilDemo { public static void main(String[] args) { A first = new A(); first.setName("demo"); first.setIds(Arrays.asList(1, 2, 3));
          B second = Converter.INSTANCE.aToB(first); for (String each : second.getIds()) {// 正常 System.out.println(each); } }}

          可以成功的將 A 中 List<Integer> 轉(zhuǎn)為 B 中的 List<String> 類型。

          我們看下編譯生成的 Converter 實現(xiàn)類:

          import java.util.ArrayList;import java.util.List;import javax.annotation.Generated;import org.springframework.stereotype.Component;
          @Generated( value = "org.mapstruct.ap.MappingProcessor", comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_202 (Oracle Corporation)")@Componentpublic class ConverterImpl implements Converter {
          @Override public B aToB(A car) { if ( car == null ) { return null; }
          B b = new B();
          b.setName( car.getName() ); b.setIds( integerListToStringList( car.getIds() ) );
          return b; }
          protected List<String> integerListToStringList(List<Integer> list) { if ( list == null ) { return null; }
          List<String> list1 = new ArrayList<String>( list.size() ); for ( Integer integer : list ) { list1.add( String.valueOf( integer ) ); }
          return list1; }}


          自動幫我們進(jìn)行了轉(zhuǎn)換,我們可能沒有意識到類型并不一致。

          如果我們在 A 類中添加一個 String number 屬性,在 B 類中添加一個 Long number 屬性,使用 mapstruect 當(dāng) number 設(shè)置為非數(shù)字類型時就會報 .NumberFormatException 。

           @Override    public B aToB(A car) {        if ( car == null ) {            return null;        }
          B b = new B();
          b.setName( car.getName() ); if ( car.getNumber() != null ) { // 問題出在這里 b.setNumber( Long.parseLong( car.getNumber() ) ); } b.setIds( integerListToStringList( car.getIds() ) );
          return b; }

          使用 cglib 默認(rèn)則不會映射 number 屬性,B 中的 number 為 null。


          如果手動定義轉(zhuǎn)換器,使用 IDEA 插件(如 generateO2O)自動轉(zhuǎn)換:

          public final class A2BConverter {
          public static B from(A first) { B b = new B(); b.setName(first.getName()); b.setIds(first.getIds()); return b; }}


          在編碼階段就可以非常明確地發(fā)現(xiàn)這個問題:

          3. 結(jié)論

          由于 Java 的泛型其實是編譯期檢查,編譯后泛型擦除,導(dǎo)致運(yùn)行時 List<Integer> 和 List<String> 都是 List 類型,可以正常賦值。這就導(dǎo)致在使用很多屬性映射工具時,編譯時不容易明顯的錯誤。

          mapstruct 自定義了注解處理器,在編譯階段可以讀取映射雙方的泛型類型,進(jìn)而進(jìn)行映射。但是這種映射也很可怕,有時候我們由于粗心等原因定義錯了類型,自動幫助我們進(jìn)行了轉(zhuǎn)換,會帶了很多副作用。

          之前對各種屬性映射工具的性能進(jìn)行了簡單的對比,結(jié)果如下:

          因此慎用屬性轉(zhuǎn)換工具,如果可能建議自定義轉(zhuǎn)換類,使用 IDEA插件自動填充,效率也挺高, A 或 B 中任何屬性類型不匹配,甚至刪除一個屬性,編譯階段即可報錯,而且直接調(diào)用 get set 的效率也是非常高的。

          希望本文對大家有幫助,創(chuàng)作不易,如果對你有幫助,歡迎點贊。
          您的支持和鼓勵是我創(chuàng)作的最大動力。


          1、最牛逼的 Java 日志框架,性能無敵,橫掃所有對手!
          2、把Redis當(dāng)作隊列來用,真的合適嗎?
          3、驚呆了,Spring Boot居然這么耗內(nèi)存!你知道嗎?
          4、牛逼哄哄的 BitMap,到底牛逼在哪?
          5、全網(wǎng)最全 Java 日志框架適配方案!還有誰不會?
          6、30個IDEA插件總有一款適合你
          7、Spring中毒太深,離開Spring我居然連最基本的接口都不會寫了

          點分享

          點收藏

          點點贊

          點在看

          瀏覽 70
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  成人天堂AV | 99亚洲婷婷伊人五月天久久欧美 | 97精品云霸高清在线视频小说 | 又黄又嫩的视频网站 | 西西444www大胆高清图片 |