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>

        SpringBoot 如何進(jìn)行對象復(fù)制,老鳥們都這么玩的!

        共 11524字,需瀏覽 24分鐘

         ·

        2021-10-02 00:16

        ??

        ??今天來聊聊在日常開發(fā)中如何優(yōu)雅的實現(xiàn)對象復(fù)制。

        首先我們看看為什么需要對象復(fù)制?

        為什么需要對象復(fù)制

        如上,是我們平時開發(fā)中最常見的三層MVC架構(gòu)模型,編輯操作時Controller層接收到前端傳來的DTO對象,在Service層需要將DTO轉(zhuǎn)換成DO,然后在數(shù)據(jù)庫中保存。查詢操作時Service層查詢到DO對象后需要將DO對象轉(zhuǎn)換成VO對象,然后通過Controller層返回給前端進(jìn)行渲染。

        這中間會涉及到大量的對象轉(zhuǎn)換,很明顯我們不能直接使用getter/setter復(fù)制對象屬性,這看上去太low了。想象一下你業(yè)務(wù)邏輯中充斥著大量的getter&setter,代碼評審時老鳥們會如何笑話你?

        所以我們必須要找一個第三方工具來幫我們實現(xiàn)對象轉(zhuǎn)換。

        看到這里有同學(xué)可能會問,為什么不能前后端都統(tǒng)一使用DO對象呢?這樣就不存在對象轉(zhuǎn)換呀?

        設(shè)想一下如果我們不想定義 DTO 和 VO,直接將 DO 用到數(shù)據(jù)訪問層、服務(wù)層、控制層和外部訪問接口上。此時該表刪除或則修改一個字段,DO 必須同步修改,這種修改將會影響到各層,這并不符合高內(nèi)聚低耦合的原則。通過定義不同的 DTO 可以控制對不同系統(tǒng)暴露不同的屬性,通過屬性映射還可以實現(xiàn)具體的字段名稱的隱藏。不同業(yè)務(wù)使用不同的模型,當(dāng)一個業(yè)務(wù)發(fā)生變更需要修改字段時,不需要考慮對其它業(yè)務(wù)的影響,如果使用同一個對象則可能因為 “不敢亂改” 而產(chǎn)生很多不優(yōu)雅的兼容性行為。

         

        對象復(fù)制工具類推薦

        對象復(fù)制的類庫工具有很多,除了常見的Apache的BeanUtils,Spring的BeanUtils,Cglib BeanCopier,還有重量級組件MapStruct,Orika,Dozer,ModelMapper等。

        如果沒有特殊要求,這些工具類都可以直接使用,除了Apache的BeanUtils。原因在于Apache BeanUtils底層源碼為了追求完美,加了過多的包裝,使用了很多反射,做了很多校驗,所以導(dǎo)致性能較差,并在阿里巴巴開發(fā)手冊上強制規(guī)定避免使用 Apache BeanUtils。

        強制規(guī)定避免使用 Apache BeanUtils

        至于剩下的重量級組件,綜合考慮其性能還有使用的易用性,我這里更推薦使用Orika。Orika底層采用了javassist類庫生成Bean映射的字節(jié)碼,之后直接加載執(zhí)行生成的字節(jié)碼文件,在速度上比使用反射進(jìn)行賦值會快很多。

        國外大神 baeldung 已經(jīng)對常見的組件性能進(jìn)行過詳細(xì)測試,大家可以通過 https://www.baeldung.com/java-performance-mapping-frameworks 查看。

         

        Orika基本使用

        要使用Orika很簡單,只需要簡單四步:

        1. 引入依賴

        <dependency>
          <groupId>ma.glasnost.orika</groupId>
          <artifactId>orika-core</artifactId>
          <version>1.5.4</version>
        </dependency>
        1. 構(gòu)造一個MapperFactory

        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();  
        1. 注冊字段映射

        mapperFactory.classMap(SourceClass.classTargetClass.class)  
           .field("firstName", "givenName")
           .field("lastName", "sirName")
           .byDefault()
           .register()
        ;

        當(dāng)字段名在兩個實體不一致時可以通過.field()方法進(jìn)行映射,如果字段名都一樣則可省略,byDefault()方法用于注冊名稱相同的屬性,如果不希望某個字段參與映射,可以使用exclude方法。

        1. 進(jìn)行映射

        MapperFacade mapper = mapperFactory.getMapperFacade();

        SourceClass source = new SourceClass();  
        // set some field values
        ...
        // map the fields of 'source' onto a new instance of PersonDest
        TargetClass target = mapper.map(source, TargetClass.class);  

        經(jīng)過上面四步我們就完成了SourceClass到TargetClass的轉(zhuǎn)換。至于Orika的其他使用方法大家可以參考 http://orika-mapper.github.io/orika-docs/index.html

        看到這里,肯定有粉絲會說:你這推薦的啥玩意呀,這個Orika使用也不簡單呀,每次都要這先創(chuàng)建MapperFactory,建立字段映射關(guān)系,才能進(jìn)行映射轉(zhuǎn)換。

        別急,我這里給你準(zhǔn)備了一個工具類OrikaUtils,你可以通過文末github倉庫獲取。

        它提供了五個公共方法:

        分別對應(yīng):
        1. 字段一致實體轉(zhuǎn)換

        2. 字段不一致實體轉(zhuǎn)換(需要字段映射)

        3. 字段一致集合轉(zhuǎn)換

        4. 字段不一致集合轉(zhuǎn)換(需要字段映射)

        5. 字段屬性轉(zhuǎn)換注冊

        接下來我們通過單元測試案例重點介紹此工具類的使用。

         

        Orika工具類使用文檔

        先準(zhǔn)備兩個基礎(chǔ)實體類,Student,Teacher。

        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        public class Student {
            private String id;
            private String name;
            private String email;
        }

        @Data
        @AllArgsConstructor
        @NoArgsConstructor
        public class Teacher {
            private String id;
            private String name;
            private String emailAddress;
        }

        TC1,基礎(chǔ)實體映射

        /**
         * 只拷貝相同的屬性
         */

        @Test
        public void convertObject(){
          Student student = new Student("1","javadaily","[email protected]");
          Teacher teacher = OrikaUtils.convert(student, Teacher.class);
          System.out.println(teacher);
        }

        輸出結(jié)果:

        Teacher(id=1, name=javadaily, emailAddress=null)

        此時由于屬性名不一致,無法映射字段email。

        TC2,實體映射 - 字段轉(zhuǎn)換

        /**
         * 拷貝不同屬性
         */

        @Test
        public void convertRefObject(){
          Student student = new Student("1","javadaily","[email protected]");

          Map<String,String> refMap = new HashMap<>(1);
          //map key 放置 源屬性,value 放置 目標(biāo)屬性
          refMap.put("email","emailAddress");
          Teacher teacher = OrikaUtils.convert(student, Teacher.classrefMap);
          System.out.println(teacher);
        }

        輸出結(jié)果:

        Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com)

        此時由于對字段做了映射,可以將email映射到emailAddress。注意這里的refMap中key放置的是源實體的屬性,而value放置的是目標(biāo)實體的屬性,不要弄反了。

        TC3,基礎(chǔ)集合映射

        /**
          * 只拷貝相同的屬性集合
          */

        @Test
        public void convertList(){
          Student student1 = new Student("1","javadaily","[email protected]");
          Student student2 = new Student("2","JAVA日知錄","[email protected]");
          List<Student> studentList = Lists.newArrayList(student1,student2);

          List<Teacher> teacherList = OrikaUtils.convertList(studentList, Teacher.class);

          System.out.println(teacherList);
        }

        輸出結(jié)果:

        [Teacher(id=1, name=javadaily, emailAddress=null), Teacher(id=2, name=JAVA日知錄, emailAddress=null)]

        此時由于屬性名不一致,集合中無法映射字段email。

        TC4,集合映射 - 字段映射

        /**
         * 映射不同屬性的集合
         */

        @Test
        public void convertRefList(){
          Student student1 = new Student("1","javadaily","[email protected]");
          Student student2 = new Student("2","JAVA日知錄","[email protected]");
          List<Student> studentList = Lists.newArrayList(student1,student2);

          Map<String,String> refMap = new HashMap<>(2);
          //map key 放置 源屬性,value 放置 目標(biāo)屬性
          refMap.put("email","emailAddress");

          List<Teacher> teacherList = OrikaUtils.convertList(studentList, Teacher.class,refMap);

          System.out.println(teacherList);
        }

        輸出結(jié)果:

        [Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com), Teacher(id=2, name=JAVA日知錄, emailAddress=jianzh5@xxx.com)]

        也可以通過這樣映射:

        Map<String,String> refMap = new HashMap<>(2);
        refMap.put("email","emailAddress");
        List<Teacher> teacherList = OrikaUtils.classMap(Student.class,Teacher.class,refMap)
                .mapAsList(studentList,Teacher.class);

        TC5,集合與實體映射

        有時候我們需要將集合數(shù)據(jù)映射到實體中,如Person類

        @Data
        public class Person {
            private List<String> nameParts;
        }

        現(xiàn)在需要將Person類nameParts的值映射到Student中,可以這樣做

        /**
         * 數(shù)組和List的映射
         */

        @Test
        public void convertListObject(){
           Person person = new Person();
           person.setNameParts(Lists.newArrayList("1","javadaily","[email protected]"));

            Map<String,String> refMap = new HashMap<>(2);
            //map key 放置 源屬性,value 放置 目標(biāo)屬性
            refMap.put("nameParts[0]","id");
            refMap.put("nameParts[1]","name");
            refMap.put("nameParts[2]","email");

            Student student = OrikaUtils.convert(person, Student.class,refMap);
            System.out.println(student);
        }

        輸出結(jié)果:

        Student(id=1, name=javadaily, email=jianzh5@163.com)

        TC6,類類型映射

        有時候我們需要類類型對象映射,如BasicPerson類

        @Data
        public class BasicPerson {
            private Student student;
        }

        現(xiàn)在需要將BasicPerson映射到Teacher

        /**
         * 類類型映射
         */

        @Test
        public void convertClassObject(){
            BasicPerson basicPerson = new BasicPerson();
            Student student = new Student("1","javadaily","[email protected]");
            basicPerson.setStudent(student);

            Map<String,String> refMap = new HashMap<>(2);
            //map key 放置 源屬性,value 放置 目標(biāo)屬性
            refMap.put("student.id","id");
            refMap.put("student.name","name");
            refMap.put("student.email","emailAddress");

            Teacher teacher = OrikaUtils.convert(basicPerson, Teacher.class,refMap);
            System.out.println(teacher);
        }

        輸出結(jié)果:

        Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com)

        TC7,多重映射

        有時候我們會遇到多重映射,如將StudentGrade映射到TeacherGrade

        @Data
        public class StudentGrade {
            private String studentGradeName;
            private List<Student> studentList;
        }

        @Data
        public class TeacherGrade {
            private String teacherGradeName;
            private List<Teacher> teacherList;
        }

        這種場景稍微復(fù)雜,Student與Teacher的屬性有email字段不相同,需要做轉(zhuǎn)換映射;StudentGrade與TeacherGrade中的屬性也需要映射。

        /**
         * 一對多映射
         */

        @Test
        public void convertComplexObject(){
          Student student1 = new Student("1","javadaily","[email protected]");
          Student student2 = new Student("2","JAVA日知錄","[email protected]");
          List<Student> studentList = Lists.newArrayList(student1,student2);

          StudentGrade studentGrade = new StudentGrade();
          studentGrade.setStudentGradeName("碩士");
          studentGrade.setStudentList(studentList);

          Map<String,String> refMap1 = new HashMap<>(1);
          //map key 放置 源屬性,value 放置 目標(biāo)屬性
          refMap1.put("email","emailAddress");
          OrikaUtils.register(Student.class,Teacher.class,refMap1);


          Map<String,String> refMap2 = new HashMap<>(2);
          //map key 放置 源屬性,value 放置 目標(biāo)屬性
          refMap2.put("studentGradeName""teacherGradeName");
          refMap2.put("studentList""teacherList");


          TeacherGrade teacherGrade = OrikaUtils.convert(studentGrade,TeacherGrade.class,refMap2);
          System.out.println(teacherGrade);
        }

        多重映射的場景需要根據(jù)情況調(diào)用OrikaUtils.register()注冊字段映射。

        輸出結(jié)果:

        TeacherGrade(teacherGradeName=碩士, teacherList=[Teacher(id=1, name=javadaily, emailAddress=jianzh5@163.com), Teacher(id=2, name=JAVA日知錄, emailAddress=jianzh5@xxx.com)])

        TC8,MyBaits plus分頁映射

        如果你使用的是mybatis的分頁組件,可以這樣轉(zhuǎn)換

        public IPage<UserDTO> selectPage(UserDTO userDTO, Integer pageNo, Integer pageSize) {
          Page page = new Page<>(pageNo, pageSize);
          LambdaQueryWrapper<User> query = new LambdaQueryWrapper();
          if (StringUtils.isNotBlank(userDTO.getName())) {
            query.like(User::getKindName,userDTO.getName());
          }
          IPage<User> pageList = page(page,query);
          // 實體轉(zhuǎn)換 SysKind轉(zhuǎn)化為SysKindDto
          Map<String,String> refMap = new HashMap<>(3);
          refMap.put("kindName","name");
          refMap.put("createBy","createUserName");
          refMap.put("createTime","createDate");
          return pageList.convert(item -> OrikaUtils.convert(item, UserDTO.classrefMap));
        }

         

        小結(jié)

        在MVC架構(gòu)中肯定少不了需要用到對象復(fù)制,屬性轉(zhuǎn)換的功能,借用Orika組件,可以很簡單實現(xiàn)這些功能。本文在Orika的基礎(chǔ)上封裝了工具類,進(jìn)一步簡化了Orika的操作,希望對各位有所幫助。


        有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)

        歡迎大家關(guān)注Java之道公眾號


        好文章,我在看??

        瀏覽 45
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        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>
            成人性生交大片免费看黄106季 | 91视频黄片 | 一边摸一边爽一边叫床免费网站 | xxx久久久 | 欧美日韩逼逼 | 日本美女操逼 | 69人人妻人人做人碰人人蜜 | 肉感大码丰满小向美奈子 | 黑人太大太长太深太硬 | 久久草在线视频 |