<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 來 copy 屬性?來試試這個(gè)吧

          共 12257字,需瀏覽 25分鐘

           ·

          2023-10-26 14:26

          在《阿里巴巴 Java 開發(fā)規(guī)范》手冊最后一節(jié)的應(yīng)用分層中推薦了應(yīng)用系統(tǒng)的分層結(jié)構(gòu)。

          我比較贊同這種分層結(jié)構(gòu),這種分層結(jié)構(gòu)帶來了諸多好處,但是有一個(gè)麻煩之處就是分層領(lǐng)域模型,也就是我們所說的各種 O,比如 DTO、POJO、DO、VO 等等,這樣就導(dǎo)致我們項(xiàng)目中存在各種屬性相同的 xxO,對于有些工作經(jīng)驗(yàn)的小伙伴們來說知道使用 BeanUtils 來實(shí)現(xiàn)屬性復(fù)制,但是對于工作經(jīng)驗(yàn)不是很多的小伙伴可能就是各種 set 和 get 了。這是非常尷尬的一件事。

          Spring 的 BeanUtils 雖然可以滿足我們大部分的需要,但是只能賦值屬性名相同且類型一致的兩個(gè)屬性,比如 VO 里面的 beginTime 是 String 類型的,而 BO 里面的 beginTime 是 Date 類型就無法賦值了。怎么解決這種問題呢?使用 Orika

          Orika 它簡化了不同層對象之間映射過程。使用字節(jié)碼生成器創(chuàng)建開銷最小的快速映射,比其他基于反射方式實(shí)現(xiàn)(如,Dozer)更快。

          簡單示例

          • 先定義兩個(gè)需要轉(zhuǎn)換的 VO 和 BO
          public class UserVO {
              private String userName;

              private Integer userAge;
          }

          public class UserBO {
              private String userName;

              private Integer userAge;    
          }
          • 測試

          Orika 的基礎(chǔ)類是 MapperFactory,其用于配置映射并獲得用于執(zhí)行映射工作的 MapperFacade 實(shí)例,如下:

          MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

          我們將 UserVO 當(dāng)做源數(shù)據(jù),將 UserBO 當(dāng)做目標(biāo)數(shù)據(jù),如下:

          public static void main(String[] args){
              MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

              mapperFactory.classMap(UserVO.class,UserBO.class);
              MapperFacade mapperFacade = mapperFactory.getMapperFacade();

              UserVO userVO = new UserVO("chenssy",18);

              UserBO userBO = mapperFacade.map(userVO,UserBO.class);

              System.out.println("userName:" + userBO.getUserName() +  " --- userAge:" + userBO.getUserAge() );
          }

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

          userName:chenssy --- userAge:18

          這只是一個(gè)比較簡單的示例,到這里,可能有小伙伴說:使用 Spring 的 BeanUtils 也可以實(shí)現(xiàn),而且代碼量更加少,更加簡單,那下面小編就演示他的高級功能,看 Spring  BeantUtils 是否還能夠?qū)崿F(xiàn)。

          使用

          使用 Orika 需要添加 maven 映射:

          <dependency>
              <groupId>ma.glasnost.orika</groupId>
              <artifactId>orika-core</artifactId>
              <version>1.4.6</version>
          </dependency>

          字段不相同映射

          上面的示例源對象和目標(biāo)對象兩者的屬性都一致,如果兩者的屬性不一致該如何處理呢?如下:

          public class UserVO {
              private String userName;

              private Integer userAge;
          }

          public class UserBO {
              private String name;

              private Integer userAge;
          }

          我們需要將 userName 的值賦值給 name,userAge 的值賦值給 age,字段映射如下:

          public static void main(String[] args){
              MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

              mapperFactory.classMap(UserVO.classUserBO.class)
                              .field("userName","name")
                              .field("userAge","userAge").register()
          ;
              MapperFacade mapperFacade = mapperFactory.getMapperFacade();

              UserVO userVO = new UserVO("chenssy-2",19);

              UserBO userBO = mapperFacade.map(userVO,UserBO.class);

              System.out.println("userName:" + userBO.getName() +  " --- userAge:" + userBO.getUserAge() );
          }

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

          userName:chenssy-2 --- userAge:19

          在這里需要注意的是:在進(jìn)行字段映射時(shí)不能忘記調(diào)用 register() 方法,它是為了給 MapperFactory 注冊配置信息的。

          如果按照上面的使用方法,則需要在注冊屬性映射時(shí)要注冊所有的屬性,哪怕只有一個(gè)屬性不一致也要注冊所有字段映射,包括相同的字段。這種方式會(huì)讓人崩潰的,比如有 20 個(gè)屬性只要 1 個(gè)不同,難道也需要配置其余 19 個(gè)相同的屬性?當(dāng)然不,我們可以通過設(shè)置缺省映射配置,這樣就無效顯示定義映射的。如下:

          mapperFactory.classMap(UserVO.classUserBO.class)
                                  .field("userName","name")
                                  .byDefault().register()
          ;

          一樣可以得到上面相同的運(yùn)行結(jié)果。

          排除字段

          在我們實(shí)際工作中,對于一個(gè) DO 賦值,我們可能只需要其中某一些字段,對于其他的字段我們需要排除掉,這個(gè)時(shí)候,我們就可以使用 exclude() 不希望該字段參與映射。比如上面實(shí)例的 userAge。如下:

          mapperFactory.classMap(UserVO.classUserBO.class)
                                  .field("userName","name")
                                  .exclude("userAge")
                                  .byDefault().register()
          ;

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

          userName:chenssy-2 --- userAge:null

          集合映射

          List 集合

          將 UserVO 集合數(shù)據(jù)拷貝到 UserBO 集合中。一般這種情況在我們實(shí)際工作中是非常多見的。

          不多說,直接上代碼。

          public static void main(String[] args){
              MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
              MapperFacade mapperFacade = mapperFactory.getMapperFacade();

              mapperFactory.classMap(UserVO.class,UserBO.class);

              List<UserVO> userList = new ArrayList<>();
              userList.add(new UserVO("chenssy_1",18));
              userList.add(new UserVO("chenssy_2",19));
              userList.add(new UserVO("chenssy_3",20));

              //進(jìn)行集合復(fù)制
              List<UserBO> userBoList = mapperFacade.mapAsList(userList,UserBO.class);

              for(UserBO userBO : userBoList){
                  System.out.println("userName:" + userBO.getUserName() +  " --- userAge:" + userBO.getUserAge() );
              }
          }

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

          userName:chenssy_1 --- userAge:18
          userName:chenssy_2 --- userAge:19
          userName:chenssy_3 --- userAge:20

          其實(shí)代碼與簡單示例中的代碼沒什么區(qū)別,僅僅只是將 map() 方法替換成了 mapAsList() 方法,至于 Map、Set,則 Orika 都提供了相應(yīng)的方法可以進(jìn)行映射,這里就不多介紹了。

          VO 中有集合

          我們可能會(huì)遇到這樣一種情況,那就是 VO 中包含著一個(gè)或者多個(gè)集合屬性,我們需要將他們的值拷貝到另一個(gè) BO 中的集合屬性中。如下:

          public class UserBOList {
              private List<UserBO> list;
          }

          public class UserVOList {
              private List<UserVO> list;
          }

          UserBOList 和 UserVOList 中包含一個(gè) List,分別是 UserBO 和 UserVO 的集合。

          public static void main(String[] args){
              MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

              // 注意這里是 List 的集合中的對象數(shù)據(jù)
              mapperFactory.classMap(UserVO.class,UserBO.class)
                      .field("userName","name")
                      .field("userAge","userAge")
                      .byDefault()
                      .register()
          ;

              List<UserVO> list = new ArrayList<>();
              list.add(new UserVO("chenssy_11",118));
              list.add(new UserVO("chenssy_22",228));
              list.add(new UserVO("chenssy_33",338));

              UserVOList userVOList = new UserVOList();
              userVOList.setList(list);

              UserBOList userBOList = mapperFactory.getMapperFacade().map(userVOList,UserBOList.class);

              for(UserBO userBO : userBOList.getList()){
                  System.out.println("userName:" + userBO.getName() +  " --- userAge:" + userBO.getUserAge() );
              }
          }

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

          userName:chenssy_11 --- userAge:118
          userName:chenssy_22 --- userAge:228
          userName:chenssy_33 --- userAge:338

          Map 轉(zhuǎn)換

          Map 轉(zhuǎn)換 Bean 是非常常見的常見,下面就演示下,如何利用 Orika 完成 Map 到 Bean 的轉(zhuǎn)換過程。

          public static void main(String[] args){
              Map<String,Object> map = new HashMap<>();
              map.put("userName","chenssy1");
              map.put("userAge",18);

              MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

              mapperFactory.classMap(HashMap.classUserBO.class)
                      .field("userName","userName")
                      .field("userAge","userAge")
                      .byDefault()
                      .register()
          ;

              UserBO userBO = mapperFactory.getMapperFacade().map(map,UserBO.class);

              System.out.println("userName:" + userBO.getUserName() +  " --- userAge:" + userBO.getUserAge() );
          }

          就是如此的簡單。

          嵌套字段映射

          加入在源數(shù)據(jù)對象中,有另外一個(gè) DTO 需要保持我們映射的值。

          這種場景還是挺多見的,不多說,直接看代碼。

          public class Person {
              private UserVO userVO;
          }

          Person 中包含 UserVO 實(shí)例。那如何將其映射到目標(biāo)對象中呢?為了訪問嵌套 DTO 的屬性并映射到目標(biāo)對象,我們只需要使用 . 即可,如下:

          public static void main(String[] args){
              MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

              mapperFactory.classMap(Person.classUserBO.class)
                      .field("userVO.userName","name")
                      .field("userVO.userAge","userAge")
                      .register()
          ;

              UserVO userVO = new UserVO("chenssy",18);
              Person person = new Person(userVO);

              UserBO userBO = mapperFactory.getMapperFacade().map(person,UserBO.class);

              System.out.println("userName:" + userBO.getName() +  " --- userAge:" + userBO.getUserAge() );
          }

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

          userName:chenssy --- userAge:18

          Bean 映射工具選擇

          以下內(nèi)容摘自:https://www.jianshu.com/p/40e0e64797b9

          • BeanUtils

          Apache的BeanUtils和spring的BeanUtils中拷貝方法的原理都是先用jdk中 java.beans.Introspector類的getBeanInfo()方法獲取對象的屬性信息及屬性get/set方法,接著使用反射(Method的invoke(Object obj, Object... args))方法進(jìn)行賦值。Apache支持名稱相同但類型不同的屬性的轉(zhuǎn)換,spring支持忽略某些屬性不進(jìn)行映射,他們都設(shè)置了緩存保存已解析過的BeanInfo信息。

          • BeanCopier

          cglib的BeanCopier采用了不同的方法:它不是利用反射對屬性進(jìn)行賦值,而是直接使用ASM的MethodVisitor直接編寫各屬性的get/set方法(具體過程可見BeanCopier類的generateClass(ClassVisitor v)方法)生成class文件,然后進(jìn)行執(zhí)行。由于是直接生成字節(jié)碼執(zhí)行,所以BeanCopier的性能較采用反射的BeanUtils有較大提高,這一點(diǎn)可在后面的測試中看出。

          • Dozer

          使用以上類庫雖然可以不用手動(dòng)編寫get/set方法,但是他們都不能對不同名稱的對象屬性進(jìn)行映射。在定制化的屬性映射方面做得比較好的有Dozer,Dozer支持簡單屬性映射、復(fù)雜類型映射、雙向映射、隱式映射以及遞歸映射。可使用xml或者注解進(jìn)行映射的配置,支持自動(dòng)類型轉(zhuǎn)換,使用方便。但Dozer底層是使用reflect包下Field類的set(Object obj, Object value)方法進(jìn)行屬性賦值,執(zhí)行速度上不是那么理想。

          • Orika

          那么有沒有特性豐富,速度又快的Bean映射工具呢.,Orika是近期在github活躍的項(xiàng)目,底層采用了javassist類庫生成Bean映射的字節(jié)碼,之后直接加載執(zhí)行生成的字節(jié)碼文件,因此在速度上比使用反射進(jìn)行賦值會(huì)快很多。


          瀏覽 628
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  男插女青青影院 | 欧美另类激情 | 大香蕉伊人97 | 蒙古妇女BBB多毛 | 成人毛片基地 |