转载

BeanCopy框架终极指南

BeanCopy框架终极指南

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

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

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

BeanCopy框架

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

  • Dozer
  • BeanCopier
  • BeanUtils
  • MapStruct
  • ModelMapper
  • Selma
  • Orika
  • JMapper

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

BeanCopy框架终极指南

框架选择

我主要推两大类

  • 基于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多了一个实现类

BeanCopy框架终极指南

同样的道理我们看看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热代码优化等原因第一次会久一点)。所以性能取舍上的考虑,主要基于量和系统场景。如果是特别夸张的并发,或者说真的系统到了需要优化类库提升性能的瓶颈上。这种低绝对值之间的相对差距才有意义,因为单次之间的差距是微秒级的,如果没有一个量的乘积放大,是可以忽略性能上的差异。正常大部分公司是没有这个需求的,没有必要追求这种极致的性能,所以考虑的更多是既处于一个"高性能"表现(绝对值),其它方面让你很满意的类库。

原文  https://juejin.im/post/5dd672e2e51d4536d737d504
正文到此结束
Loading...