1. 聽說你還在使用 BeanUtils 來 copy 屬性?來試試這個吧

        共 12257字,需瀏覽 25分鐘

         ·

        2023-10-26 14:26

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

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

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

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

        簡單示例

        • 先定義兩個需要轉換的 VO 和 BO
        public class UserVO {
            private String userName;

            private Integer userAge;
        }

        public class UserBO {
            private String userName;

            private Integer userAge;    
        }
        • 測試

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

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

        我們將 UserVO 當做源數(shù)據,將 UserBO 當做目標數(shù)據,如下:

        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() );
        }

        運行結果:

        userName:chenssy --- userAge:18

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

        使用

        使用 Orika 需要添加 maven 映射:

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

        字段不相同映射

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

        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() );
        }

        運行結果如下:

        userName:chenssy-2 --- userAge:19

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

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

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

        一樣可以得到上面相同的運行結果。

        排除字段

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

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

        運行結果如下:

        userName:chenssy-2 --- userAge:null

        集合映射

        List 集合

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

        不多說,直接上代碼。

        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));

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

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

        運行結果:

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

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

        VO 中有集合

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

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

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

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

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

            // 注意這里是 List 的集合中的對象數(shù)據
            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() );
            }
        }

        運行結果:

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

        Map 轉換

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

        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ù)據對象中,有另外一個 DTO 需要保持我們映射的值。

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

        public class Person {
            private UserVO userVO;
        }

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

        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() );
        }

        運行結果:

        userName:chenssy --- userAge:18

        Bean 映射工具選擇

        以下內容摘自:https://www.jianshu.com/p/40e0e64797b9

        • BeanUtils

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

        • BeanCopier

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

        • Dozer

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

        • Orika

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


        瀏覽 628
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
          
          

            1. 肏屄一区 | 性姿势囗交109图片 | 国产 高清秘 成人久久 | 久久久久久99精品久久久 | 色就操 |