Java object transformation and mapstruct practice

1 Preface

In daily development, we often need to assign values to objects and usually call their set/get methods. Sometimes, if the properties of two objects we want to convert are roughly the same, we will consider using the property copy tool.
For example, we often encapsulate a data structure into DO, PO, DTO, VO, etc. in the code, and most of the attributes in these beans are the same, so using attribute copy tools can help us save a lot of set and get operations.

2 common tools

There are many similar tools on the market, and the commonly used ones are as follows:

 

 

So, which tool class should we choose more appropriate? Why does Alibaba's Java development manual mention banning the use of Apache BeanUtils?

3 performance test comparison

In the process of Java system engineering development, there will be object conversion between various layers, such as VO, DTO, PO, VO, etc. if they are all manual get and set, it will be too time-consuming and may operate incorrectly. Therefore, it will be more convenient to choose an automatic tool.

At present, there are 12 kinds of object attribute conversion, including: ordinary getset, json2Json, Apache attribute copy, Spring attribute copy, bean mapping, bean mapping ASM, BeanCopier, Orika, Dozer, ModelMapper, JMapper and MapStruct. Next, we test the performance and time comparison of these 11 kinds of attribute conversion operations at 100 times, 1000 times, 10000 times, 100000 times and 1 million times respectively.

  • BeanUtils.copyProperties , is the most common tool class in your code, but as long as you don't mistake it for the one under Apache package, but use the one provided by Spring, it won't have much impact on performance.
  • However, if the performance is better and can replace manual get and set, MapStruct is better because it generates get and set code during compilation, just like we write get and set.
  • Other component packages are mainly implemented based on AOP, ASM and CGlib, so there will be corresponding performance loss.

4 case introduction

4.1 get\set

@Component
public class GetSetAssembler implements IAssembler<UserVO, UserDTO> {

    @Override
    public UserDTO sourceToTarget(UserVO var) {
        UserDTO userDTO = new UserDTO();
        userDTO.setUserId(var.getUserId());
        userDTO.setUserNickName(var.getUserNickName());
        userDTO.setCreateTime(var.getCreateTime());
        return userDTO;
    }

}
  • Recommendation: ★★★☆☆
  • Performance: ★★★★★
  • Means: handwriting
  • Comments: in fact, this method is also the most commonly used. The performance must be lever, but it is a little troublesome to operate. Especially when VO objects with a lot of attributes are converted into DTO objects. But there are also some quick operation methods. For example, you can select all attributes through Shift+Alt, merge Shift+Tab into one column, and then use ALT to select this column and paste userdto in batch Set , and the shortcut key capitalize the initial of the attribute. Finally, switch to the end and add parentheses and semicolons. Finally, format it

4.2. fastjson

The performance of this scheme is very poor because the intermediate json format string is generated and then converted into the target object. At the same time, because the intermediate json format string will be generated, if the conversion is too much, gc will be very frequent. At the same time, it has insufficient support ability for complex scenes, so it is basically rarely used.

@Component
public class Json2JsonAssembler implements IAssembler<UserVO, UserDTO> {

    @Override
    public UserDTO sourceToTarget(UserVO var) {
        String strJson = JSON.toJSONString(var);
        return JSON.parseObject(strJson, UserDTO.class);
    }

}
  • Recommendation: ☆☆☆☆☆
  • Performance: ★☆☆☆☆
  • Means: convert the object to JSON string, and then convert JSON to another object
  • Comments: it's probably a little burnt!

4.3 Apache copyProperties

BeanUtil.copyProperties() combines handwritten get and set. For simple conversion, directly use beanutil. For complex conversion, write get and set manually. The pain point of this scheme is that the coding efficiency is low, the redundancy is complicated and slightly ugly, and the performance of beanutil is not high because it uses reflection invoke to assign values. It can only be used in scenarios with a small number of beans, few contents and infrequent conversion.

@Component
public class ApacheCopyPropertiesAssembler implements IAssembler<UserVO, UserDTO> {

    @Override
    public UserDTO sourceToTarget(UserVO var) {
        UserDTO userDTO = new UserDTO();
        try {
            BeanUtils.copyProperties(userDTO, var);
        } catch (IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return userDTO;
    }

}
  • Recommendation: ☆☆☆☆☆
  • Performance: ★☆☆☆☆
  • Means: the Introspector mechanism obtains the attributes of the class for assignment
  • Comments: there are pits and poor compatibility. It is not recommended to use

4.4 Spring copyProperties

This scheme has made many optimizations for apache's BeanUtils, and the overall performance has been improved a lot. However, the reflection implementation is still not as good as the native code processing. Secondly, it has insufficient support for complex scenes.

@Component
public class SpringCopyPropertiesAssembler implements IAssembler<UserVO, UserDTO> {

    @Override
    public UserDTO sourceToTarget(UserVO var) {
        UserDTO userDTO = new UserDTO();
        BeanUtils.copyProperties(var, userDTO);
        return userDTO;
    }

}
  • Recommendation: ★★★☆☆
  • Performance: ★★★★★
  • Means: the Introspector mechanism obtains the attributes of the class for assignment
  • Comments: it is also a copy of reflective properties. The copyProperties provided by Spring are much easier to use than Apache. As long as you don't make mistakes, there will be no problem.

4.5  Bean Mapping

@Component
public class BeanMappingAssembler implements IAssembler<UserVO, UserDTO> {

    @Override
    public UserDTO sourceToTarget(UserVO var) {
        UserDTO userDTO = new UserDTO();
        BeanUtil.copyProperties(var, userDTO);
        return userDTO;
    }

}
  • Recommendation: ★★☆☆☆
  • Performance: ★★★☆☆
  • Means: attribute copy
  • Comments: average performance

4.6  Bean Mapping ASM

@Component
public class BeanMappingAssembler implements IAssembler<UserVO, UserDTO> {

    @Override
    public UserDTO sourceToTarget(UserVO var) {
        UserDTO userDTO = new UserDTO();
        BeanUtil.copyProperties(var, userDTO);
        return userDTO;
    }

}
  • Recommendation: ★★★☆☆
  • Performance: ★★★★★
  • Means: Based on ASM bytecode framework
  • Comments: compared with ordinary Bean Mapping, the performance is improved and can be used.

4.7  BeanCopier

This scheme dynamically generates a subclass of the class to be proxy, which is actually converted into the best performance get and set methods through bytecode. The important overhead is to create BeanCopier. The overall performance is close to the native code processing, which is much better than BeanUtils, especially when there is a large amount of data, but the support ability for complex scenes is insufficient.

@Component
public class BeanCopierAssembler implements IAssembler<UserVO, UserDTO> {

    @Override
    public UserDTO sourceToTarget(UserVO var) {
        UserDTO userDTO = new UserDTO();
        BeanCopier beanCopier = BeanCopier.create(var.getClass(), userDTO.getClass(), false);
        beanCopier.copy(var, userDTO, null);
        return userDTO;
    }

}
  • Recommendation: ★★★☆☆
  • Performance: ★★★★★
  • Means: generate get and set methods based on CGlib bytecode operation
  • Comments: the overall performance is very good and the use is not complicated. It can be used

4.8  Orika

@Component
public class OrikaAssembler implements IAssembler<UserVO, UserDTO> {

    /**
     * Construct a MapperFactory
     */
    private static MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();

    static {
        mapperFactory.classMap(UserDTO.class, UserVO.class)
                .field("userId", "userId")  // You can specify when the fields are inconsistent
                .byDefault()
                .register();
    }

    @Override
    public UserDTO sourceToTarget(UserVO var) {
        return mapperFactory.getMapperFacade().map(var, UserDTO.class);
    }

}
  • Official website: https://orika-mapper.github.io/orika-docs/
  • Recommendation: ★★☆☆☆
  • Performance: ★★★☆☆
  • Method: generate mapping object based on bytecode
  • Comments: the test performance is not too outstanding. If you use it, you need to optimize the construction of MapperFactory into Bean objects

4.9 Dozer

@Component
public class DozerAssembler implements IAssembler<UserVO, UserDTO> {

    private static DozerBeanMapper mapper = new DozerBeanMapper();

    @Override
    public UserDTO sourceToTarget(UserVO var) {
        return mapper.map(var, UserDTO.class);
    }

}

4.10  ModelMapper

@Component
public class ModelMapperAssembler implements IAssembler<UserVO, UserDTO> {

    private static ModelMapper modelMapper = new ModelMapper();

    static {
        modelMapper.addMappings(new PropertyMap<UserVO, UserDTO>() {
            @Override
            protected void configure() {
                // Different attribute values can be operated by yourself
                map().setUserId(source.getUserId());
            }
        });
    }

    @Override
    public UserDTO sourceToTarget(UserVO var) {
        return modelMapper.map(var, UserDTO.class);
    }

}
  • Official website: http://modelmapper.org
  • Recommendation: ★★★☆☆
  • Performance: ★★★☆☆
  • Means: Based on ASM bytecode
  • Comments: the performance is good when the number of conversion objects is small. If the objects are converted in large quantities at the same time, the performance will decline

4.11 JMapper

JMapper<UserDTO, UserVO> jMapper = new JMapper<>(UserDTO.class, UserVO.class, new JMapperAPI()
        .add(JMapperAPI.mappedClass(UserDTO.class)
                .add(JMapperAPI.attribute("userId")
                        .value("userId"))
                .add(JMapperAPI.attribute("userNickName")
                        .value("userNickName"))
                .add(JMapperAPI.attribute("createTime")
                        .value("createTime"))
        ));
  • Official website: https://github.com/jmapper-framework/jmapper-core/wiki
  • Recommendation: ★★★★★
  • Performance: ★★★★★
  • Means: element, high performance and robustness all in one java bean mapper
  • Comments: the speed is really OK, but combined with SpringBoot, I feel a little trouble. Maybe the posture is wrong

4.12 MapStruct

maven dependency:

<properties>
 <org.mapstruct.version>1.3.1.Final</org.mapstruct.version>
</properties>

<dependencies>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version>
            <scope>provided</scope>
        </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>${lombok.version}</version>
                    </path>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

Note: configure lombok here, otherwise the startup will conflict.

Code implementation:

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE, unmappedSourcePolicy = ReportingPolicy.IGNORE)
public interface UserDTOMapping extends IMapping<UserVO, UserDTO> {

    /** Single example for testing */
    IMapping<UserVO, UserDTO> INSTANCE = Mappers.getMapper(UserDTOMapping.class);

    @Mapping(target = "userId", source = "userId")
    @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Override
    UserDTO sourceToTarget(UserVO var1);

    @Mapping(target = "userId", source = "userId")
    @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Override
    UserVO targetToSource(UserDTO var1);

}

be careful:

1: You can add @ Mapper(componentModel = "spring") to the Mapping class and introduce the Mapper interface through injection

2: You can use mappers Get Mapper interface by getmapper (userdtomapping. Class)

  • Official website: https://github.com/mapstruct/mapstruct,ModelMapper - Getting Started
  • Recommendation: ★★★★★
  • Performance: ★★★★★
  • Means: directly generate the corresponding get and set during compilation, just like handwritten code
  • Comments: the speed is very fast. It does not need to be processed in the operation period. It is easy to use when combined into the framework

5 Summary

  • In fact, the operation of object attribute conversion is based on reflection, AOP, CGlib, ASM and Javassist, which are processed at compile time and runtime. Another good idea is to generate the corresponding get and set before compilation, just like handwritten.
  • So I prefer MapStruct, which I like. It's comfortable to use. One comes from the expansibility, ease of use and compatibility of functions.

reference resources:

12 vo2dto methods (qq.com)

Java object transformation scheme analysis and mapstruct practice - Alibaba cloud developer community (aliyun.com)

MapStruct 1.4.2.Final Reference Guide

mapstruct/mapstruct-examples: Examples for using MapStruct (github.com)

Why does Alibaba prohibit using Apache Beanutils to copy attributes? (qq.com)

Kill BeanUtils! Try this Bean automatic mapping tool. It's really powerful- SegmentFault no

Tags: mapstruct

Posted by solar_ninja on Sun, 15 May 2022 13:03:14 +0300