美文网首页Java web
BeanCopy框架终极指南

BeanCopy框架终极指南

作者: 但时间也偷换概念 | 来源:发表于2019-11-21 19:09 被阅读0次

    前言

    image.png

    如图所示,在开发之中,无论是MVC式的三层架构,还是DDD领域驱动式的架构。总会有各种DTO、DO、PO、VO之间的转换需求。所以我们经常会定义两层Object字段是保持一致的,便于防腐层Assember操作。但现实需求中也会遇到一些复杂映射。所以我们应该如何基于场景选择合适的BeanCopy框架呢?

    这篇博客主要整理一下BeanCopy类框架。

    • 各个框架性能表现
    • 如何选择

    BeanCopy框架

    除了HardCopy之外(手写set get)
    常用的BeanCopy选择有以下:

    我直接给出一个performance报告
    BeanCopy框架性能对比
    结论图:

    image.png

    框架选择

    我主要推两大类

    • 基于MapStruct*Selma的注解式Mapper
      MapStruct和Selma都是基于注解处理器实现的,关于注解处理器我单独写一篇blog介绍,到时候在这里新增链接。
      MapStruct是基于JSR 269的Java注解处理器,在使用过程中需要只需要配置完成后运行 mvn compile就会发现 target文件夹中生成了一个mapper接口的实现类。打开实现类会发现实体类中自动生成了字段一一对应的get、set方法的文件。
      比如我定义一个MapStruct接口(@Mapper注解支持IOC注入方式、我这里没使用)
    @Mapper
    public interface PersonMapper {
        PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class);
    
        /**
         * source -> destination
         *
         * @param car
         * @return
         */
        @Mappings({
            @Mapping(source = "middleName", target = "middle"),
            @Mapping(target = "email", ignore = true)
        })
        PersonDestination sourceToDestination(PersonSource car);
    }
    
    

    编译之后你会发现target多了一个实现类


    image.png

    同样的道理我们看看Selma

    @Mapper
    public interface SelmaPersonMapper {
        SelmaPersonMapper INSTANCE = Selma.mapper(SelmaPersonMapper.class);
    
        /**
         * source -> destination
         *
         * @param car
         * @return
         * @Maps(withCustomFields = {
         * @Field({"middleName", "middle"})
         * }, withIgnoreFields = {"email"})
         */
        @Maps(withCustomFields = {
            @Field({"middleName", "middle"})
        }, withIgnoreFields = {"email"})
        PersonDestination sourceToDestination(PersonSource car);
    }
    
    

    所以Selma和MapStruct是非常相似的,原理一样,并且在注解和用法上几乎一样,我认为MapStruct更好的原因主要是社区更活跃,与SpringBoot集成更好,并且生成的代码更规范、简洁、漂亮。

    • 基于Orika、JMapper的静态工具类(Dozer性能太差舍弃)
      封装一下以下代码即可。
      private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
            MapperFacade mapper = mapperFactory.getMapperFacade();
            PersonDestination orikaDestination = mapper.map(source, PersonDestination.class);
    

    如果是List互相转换

     private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
            MapperFacade mapper = mapperFactory.getMapperFacade();
            List<PersonSource> sourceList = Lists.newArrayList(source);
            List<PersonDestination> personDestinations = mapper.mapAsList(sourceList, PersonDestination.class);
    

    如果是字段名有映射的

     private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
            mapperFactory.classMap(PersonSource.class, PersonDestination.class)
                .field("firstName", "givenName")
                .field("lastName", "sirName")
                .byDefault()
                .register();
            MapperFacade mapper = mapperFactory.getMapperFacade();
            PersonDestination destination = mapper.map(source, PersonDestination.class);
    
    

    实验

    @NoArgsConstructor
    @AllArgsConstructor
    @Setter
    @Getter
    @ToString
    public class PersonSourceComputer {
        private String name;
        private BigDecimal price;
    }
    
    
    @NoArgsConstructor
    @AllArgsConstructor
    @Setter
    @Getter
    @ToString
    public class PersonSourceSon {
        private String sonName;
        private List<PersonSourceComputer> computers;
    }
    
    
    @NoArgsConstructor
    @AllArgsConstructor
    @Setter
    @Getter
    @ToString
    public class PersonSource {
        private String firstName;
        private String middleName;
        private String lastName;
        private String email;
        List<PersonSourceSon> son;
    
    }
    
    public class BeanCopyTest {
        private static final Logger logger = LoggerFactory.getLogger(BeanCopyTest.class);
        private static final MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
    
        //static {
        //    mapperFactory.classMap(PersonSource.class, PersonDestination.class).byDefault().register();
        //}
    
        public static void main(String[] args) {
    
            for (int i = 1; i < 11; i++) {
                beanCopyTest(i);
            }
    
        }
    
        private static void beanCopyTest(int i) {
            PersonSource source = initAndGetPersonSource();
    
            Stopwatch stopwatch = Stopwatch.createStarted();
            // MapStruct
            PersonDestination destination = PersonMapper.INSTANCE.sourceToDestination(source);
            System.out.println(destination);
            stopwatch.stop();
            logger.info("第" + i + "次" + "MapStruct cost:" + stopwatch.toString());
    
            // Selma
            stopwatch = Stopwatch.createStarted();
            PersonDestination selmaDestination = SelmaPersonMapper.INSTANCE.sourceToDestination(source);
            System.out.println(selmaDestination);
            stopwatch.stop();
            logger.info("第" + i + "次" + "Selma cost:" + stopwatch.toString());
    
            // BeanUtils
            stopwatch = Stopwatch.createStarted();
            PersonDestination bUtilsDestination = new PersonDestination();
            BeanUtils.copyProperties(source, bUtilsDestination);
            System.out.println(bUtilsDestination);
            stopwatch.stop();
            logger.info("第" + i + "次" + "BeanUtils cost:" + stopwatch.toString());
    
            // BeanCopier
            stopwatch = Stopwatch.createStarted();
            BeanCopier beanCopier = BeanCopier.create(PersonSource.class, PersonDestination.class, false);
            PersonDestination bcDestination = new PersonDestination();
            beanCopier.copy(source, bcDestination, null);
            System.out.println(bcDestination);
            stopwatch.stop();
            logger.info("第" + i + "次" + "BeanCopier cost:" + stopwatch.toString());
    
            // Orika
            stopwatch = Stopwatch.createStarted();
            MapperFacade mapper = mapperFactory.getMapperFacade();
            PersonDestination orikaDestination = mapper.map(source, PersonDestination.class);
            System.out.println(orikaDestination);
            stopwatch.stop();
            logger.info("第" + i + "次" + "Orika cost:" + stopwatch.toString());
        }
    
        private static PersonSource initAndGetPersonSource() {
            PersonSource source = new PersonSource();
            // set some field values
            source.setFirstName("firstName");
            source.setMiddleName("middleName");
            source.setLastName("lastName");
            source.setEmail("email");
            source.setSon(Lists.newArrayList(new PersonSourceSon(
                "sonName", Lists.newArrayList(new PersonSourceComputer("macBook", BigDecimal.valueOf(15000)))
            )));
            return source;
        }
    }
    
    
    17:56:30.029 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次MapStruct cost:4.179 ms
    17:56:30.035 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次Selma cost:2.727 ms
    17:56:30.095 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次BeanUtils cost:59.65 ms
    17:56:30.139 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次BeanCopier cost:43.52 ms
    17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次Orika cost:167.1 ms
    17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次MapStruct cost:88.97 μs
    17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次Selma cost:36.72 μs
    17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次BeanUtils cost:68.76 μs
    17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次BeanCopier cost:62.75 μs
    17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次Orika cost:108.0 μs
    17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次MapStruct cost:63.29 μs
    17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次Selma cost:71.12 μs
    17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次BeanUtils cost:81.64 μs
    17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次BeanCopier cost:68.01 μs
    17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次Orika cost:112.4 μs
    17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次MapStruct cost:54.27 μs
    17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次Selma cost:37.97 μs
    17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次BeanUtils cost:124.3 μs
    17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次BeanCopier cost:124.9 μs
    17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次Orika cost:107.3 μs
    17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次MapStruct cost:43.39 μs
    17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次Selma cost:50.03 μs
    17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次BeanUtils cost:75.00 μs
    17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次BeanCopier cost:50.83 μs
    17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次Orika cost:92.18 μs
    17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次MapStruct cost:34.60 μs
    17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次Selma cost:61.26 μs
    17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次BeanUtils cost:118.6 μs
    17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次BeanCopier cost:102.7 μs
    17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次Orika cost:170.5 μs
    17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次MapStruct cost:65.29 μs
    17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次Selma cost:52.06 μs
    17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次BeanUtils cost:86.51 μs
    17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次BeanCopier cost:101.3 μs
    17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次Orika cost:119.0 μs
    17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次MapStruct cost:56.88 μs
    17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次Selma cost:35.56 μs
    17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次BeanUtils cost:98.93 μs
    17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次BeanCopier cost:69.25 μs
    17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次Orika cost:95.27 μs
    17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次MapStruct cost:33.60 μs
    17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次Selma cost:31.90 μs
    17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次BeanUtils cost:96.19 μs
    17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次BeanCopier cost:77.15 μs
    17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次Orika cost:95.17 μs
    17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次MapStruct cost:45.55 μs
    17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Selma cost:32.28 μs
    17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次BeanUtils cost:62.06 μs
    17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次BeanCopier cost:46.78 μs
    17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Orika cost:113.6 μs
    
    
    17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次Orika cost:167.1 ms
    17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次Orika cost:108.0 μs
    17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次Orika cost:112.4 μs
    17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次Orika cost:107.3 μs
    17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次Orika cost:92.18 μs
    17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次Orika cost:170.5 μs
    17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次Orika cost:119.0 μs
    17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次Orika cost:95.27 μs
    17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次Orika cost:95.17 μs
    17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Orika cost:113.6 μs
    
    
    17:56:30.029 [main] INFO com.alibaba.beancp.BeanCopyTest - 第1次MapStruct cost:4.179 ms
    17:56:30.306 [main] INFO com.alibaba.beancp.BeanCopyTest - 第2次MapStruct cost:88.97 μs
    17:56:30.307 [main] INFO com.alibaba.beancp.BeanCopyTest - 第3次MapStruct cost:63.29 μs
    17:56:30.308 [main] INFO com.alibaba.beancp.BeanCopyTest - 第4次MapStruct cost:54.27 μs
    17:56:30.309 [main] INFO com.alibaba.beancp.BeanCopyTest - 第5次MapStruct cost:43.39 μs
    17:56:30.310 [main] INFO com.alibaba.beancp.BeanCopyTest - 第6次MapStruct cost:34.60 μs
    17:56:30.311 [main] INFO com.alibaba.beancp.BeanCopyTest - 第7次MapStruct cost:65.29 μs
    17:56:30.312 [main] INFO com.alibaba.beancp.BeanCopyTest - 第8次MapStruct cost:56.88 μs
    17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第9次MapStruct cost:33.60 μs
    17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次MapStruct cost:45.55 μs
    
    
    17:56:30.313 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次MapStruct cost:45.55 μs
    PersonDestination(firstName=firstName, middle=middleName, lastName=lastName, email=null, son=[PersonDestinationSon(sonName=sonName, computers=[PersonDestinationComputer(name=macBook, price=15000)])])
    17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Selma cost:32.28 μs
    PersonDestination(firstName=firstName, middle=null, lastName=lastName, email=email, son=[PersonSourceSon(sonName=sonName, computers=[PersonSourceComputer(name=macBook, price=15000)])])
    17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次BeanUtils cost:62.06 μs
    PersonDestination(firstName=firstName, middle=null, lastName=lastName, email=email, son=[PersonSourceSon(sonName=sonName, computers=[PersonSourceComputer(name=macBook, price=15000)])])
    17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次BeanCopier cost:46.78 μs
    PersonDestination(firstName=firstName, middle=null, lastName=lastName, email=email, son=[PersonDestinationSon(sonName=sonName, computers=[PersonDestinationComputer(name=macBook, price=15000)])])
    17:56:30.314 [main] INFO com.alibaba.beancp.BeanCopyTest - 第10次Orika cost:113.6 μs
    
    

    实验结论:

    1.不管使用实验中的哪种框架,在性能上其实绝对值相差不会太大(非第一次运行)。
    2.个别框架拷贝后引用是PersonSourceSon,个别是PersonDestinationSon,说明不同框架在深浅拷贝方案上实现不同。
    3.有字段名映射、ignore、格式化等需求时,不同框架支持的不同。

    总结

    1.在日常开发中,BeanCopy需求无非是三种

    • 字段相同最简单的copy
    • 有复杂性的copy(比如字段名称不同、有ignore需求、有格式化需求)
    • 有业务逻辑的copy

    那么针对以上三点,我认为

    • 第一种以简单高效为主,我建议直接使用Orika工具类,实现非常简单,客户端编码非常少,基本上就是丢一个source和target type进去即可,保证了深拷贝,性能上高于Dozer等老产品,并且集合之间拷贝也很优秀。像BeanUtils、BeanCopier在很多场景表现明显不如Orika,会有各种问题备受吐槽。
    • 第二种建议使用功能强大的MapStruct框架,它的好处呢,就是既生成了代码,比较直观方便debug。又支持非常多且强大的注解,可以轻松做到多层级之间字段映射、字段ignore、日期格式化、金额格式化等。还有mapping模版继承复用、组合等功能。还有就是天然支持Spring注入,SpringBoot集成等,在这一点上,相比较Dozer式的xml映射,注解是更符合现代编程方式的。

    2.关于性能的取舍

    我们通过性能测试可以发现,一旦运行过一次之后,上面几种框架单次copy性能绝对值都非常低(个别框架主要基于Asm开始的耗时、缓存原理、jvm热代码优化等原因第一次会久一点)。所以性能取舍上的考虑,主要基于量和系统场景。如果是特别夸张的并发,或者说真的系统到了需要优化类库提升性能的瓶颈上。这种低绝对值之间的相对差距才有意义,因为单次之间的差距是微秒级的,如果没有一个量的乘积放大,是可以忽略性能上的差异。正常大部分公司是没有这个需求的,没有必要追求这种极致的性能,所以考虑的更多是既处于一个"高性能"表现(绝对值),其它方面让你很满意的类库。

    相关文章

      网友评论

        本文标题:BeanCopy框架终极指南

        本文链接:https://www.haomeiwen.com/subject/gnitwctx.html