BeanCopy框架終極指南

前言

image.png

如圖所示,在開發之中,不管是MVC式的三層架構,仍是DDD領域驅動式的架構。總會有各類DTO、DO、PO、VO之間的轉換需求。因此咱們常常會定義兩層Object字段是保持一致的,便於防腐層Assember操做。但現實需求中也會遇到一些複雜映射。因此咱們應該如何基於場景選擇合適的BeanCopy框架呢?java

這篇博客主要整理一下BeanCopy類框架。git

  • 各個框架性能表現
  • 如何選擇

BeanCopy框架

除了HardCopy以外(手寫set get) 經常使用的BeanCopy選擇有如下:github

我直接給出一個performance報告 BeanCopy框架性能對比 結論圖: spring

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

一樣的道理咱們看看Selmabash

@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);
複製代碼

若是是字段名有映射的app

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熱代碼優化等緣由第一次會久一點)。因此性能取捨上的考慮,主要基於量和系統場景。若是是特別誇張的併發,或者說真的系統到了須要優化類庫提高性能的瓶頸上。這種低絕對值之間的相對差距纔有意義,由於單次之間的差距是微秒級的,若是沒有一個量的乘積放大,是能夠忽略性能上的差別。正常大部分公司是沒有這個需求的,沒有必要追求這種極致的性能,因此考慮的更可能是既處於一個"高性能"表現(絕對值),其它方面讓你很滿意的類庫。

相關文章
相關標籤/搜索