Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

6 object replication tool classes, how to choose?


Jun 01, 2021 Article blog


Table of contents


In our daily work, when we inevitably encounter object property replication, here's an example of a common three-tier MVC architecture.

 6 object replication tool classes, how to choose?1

When we program under the architecture above, we often need to go through object transformations, such as the business request process that goes through three layers of organization and then turns DTO into DO and then saves it in the database.

When you need to present from the data query data page, the query data goes through a three-tier architecture that moves from DO to DTO then to VO and then to the page.

When the business is simple, we write the code and copy the object properties through getter/setter which is very simple. But once the business becomes more complex and object properties become numerous, handwritten code can become a programmer's nightmare.

Not only is handwriting cumbersome, time consuming, and can be error-prone.

Small editor has experienced a project before, an object has about forty or fifty field properties, when the small editor is just getting started, do not understand anything, wrote a half-day getter/setter copy object properties.

Voiceover: There are so many attributes of an object that it is obviously not reasonable that our design process should split it.

It wasn't until later that the editor learned about the object property copy tool class, and after using it, it was found to be genuine and no longer written code by hand. L ater, there are more and more tool classes, although the core functions are the same, but there are still many differences. Novices may look forced to see, do not know how to choose.

So today's editor-in-chief introduces the tools commonly used on the market:

  • Apache BeanUtils
  • Spring BeanUtils
  • Cglib BeanCopier
  • Dozer
  • orika
  • MapStruct

Tool class characteristics

Before we get into these tool classes, let's look at the next useful property replication tool class, and what features are needed:

  • Basic property replication, this is the basic function
  • Different types of property assignments, such as basic types and their wrapper types
  • Different field name property assignments, of course, field names should try to be consistent, but in practice, because of different developers, or pen misspelled words, these reasons can lead to inconsistent field names
  • Shallow copy/deep copy, shallow copy will refer to the same object, if slightly careless, while changing the object, will step on the unexpected pit

Let's start with the tool class.

(Recommended tutorial: Java tutorial)

Apache BeanUtils

The first introduction is the first tool class that should be Java domain property replication, Apache BeanUtils, which many people must have used or seen more or less.

It doesn't matter if you haven't used it, let's show you how to use this class, it's very simple to use.

First we introduce dependencies, here's the latest version:

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

Let's say we have the following classes in our project:

 6 object replication tool classes, how to choose?2

At this point we need to complete the conversion of the DTO object to the DO object, and we can copy the object properties by simply calling the BeanUtils#copyProperties method.

StudentDTO studentDTO = new StudentDTO();
studentDTO.setName("小编");
studentDTO.setAge(18);
studentDTO.setNo("6666");


List<String> subjects = new ArrayList<>();
subjects.add("math");
subjects.add("english");
studentDTO.setSubjects(subjects);
studentDTO.setCourse(new Course("CS-1"));
studentDTO.setCreateDate("2020-08-08");


StudentDO studentDO = new StudentDO();


BeanUtils.copyProperties(studentDO, studentDTO);

However, if you write this code above, we'll run into the first problem, beanUtils don't support String to Date type by default.

 6 object replication tool classes, how to choose?3

To solve this problem, we need to construct a Converter conversion class ourselves and then register with ConvertUtils using the following methods:

ConvertUtils.register(new Converter() {
    @SneakyThrows
    @Override
    public <Date> Date convert(Class<Date> type, Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof String) {
            String str = (String) value;
            return (Date) DateUtils.parseDate(str, "yyyy-MM-dd");
        }
        return null;




    }
}, Date.class);

At this point, let's look at studentDO and studentDTO object property values:

 6 object replication tool classes, how to choose?4

From the figure above, we can draw some conclusions from BeanUtils:

  • Properties with inconsistent normal field names cannot be copied
  • Nested object fields will use the same object as the source object, i.e. shallow copies
  • Fields with inconsistent types will be converted by default type.

Although BeanUtils is easy to use, its underlying source code in pursuit of perfection, added too much packaging, used a lot of reflections, did a lot of checks, resulting in poor performance, so Alibaba development manual mandatory to avoid the use of Apache BeanUtils.

 6 object replication tool classes, how to choose?5

Spring BeanUtils

The Spring property copy tool class name is similar to Apache and the basic usage is similar. Let me first look at the basic usage of Spring BeanUtils.

Again, let's introduce dependencies, and as you can see from the name, BeanUtils is located in the Spring-Beans module, where we still use the latest modules.

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

Here we use the example above for DTO and DO reuse, and the conversion code is as follows:

// 省略上面赋值代码,与上面一致
StudentDO studentDO = new StudentDO();


BeanUtils.copyProperties(studentDTO, studentDO);

As you can see from the usage, Spring BeanUtils and Apache have one of the biggest differences, the source object and the target object parameter location is not the same, the small editor did not notice before, used the Spring tool class, but is used in accordance with the use of Apache.

Compare studentDO with the studentDTO object:

 6 object replication tool classes, how to choose?6

From the comparison chart above, we can draw some conclusions:

  • The field names are inconsistent and the property cannot be copied
  • The type is inconsistent and the property cannot be copied. Note, however, that this can be converted if the type is the basic type and the basic type of wrapper class
  • Nested object fields will use the same object as the source object, i.e. shallow copies

In addition to this method, Spring BeanUtils provides an overload method:

public static void copyProperties(Object source, Object target, String... ignoreProperties) 

Using this method, we can ignore some properties that do not want to be copied in the past:

BeanUtils.copyProperties(studentDTO, studentDO,"name");

This way, name property is not copied to the DO object.

 6 object replication tool classes, how to choose?7

Although Spring BeanUtils is similar to Apache BeanUtils, Spring BeanUtils is still the perfect apache Bean Utils in performance. The main reason is that Spring doesn't use reflections as much as Apache, and Spring Bean Utils uses caching internally to speed up conversion.

So choose between the two, or recommend Spring BeanUtils.

Cglib BeanCopier

The top two are often used by small editors, and the following are small editors who have only recently come into contact, such as Cglib BeanCopier. This method of use may be a little more complex than the two classes above, let's look at the specific usage:

First we introduce Cglib dependencies:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

Voiceover: If you still have Spring-Core in your project, if you look for BeanCopier class, you can find two different packages with the same name.

One belongs to Cglib and the other belongs to Spring-Core.

In fact, BeanCopier within Spring-Core actually introduced classes in Cglib to ensure the stability of Spring's use of length Cglib-related classes, preventing external Cglib dependencies from becoming inconsistent and causing Spring to run abnormally.

The conversion code is as follows:

// 省略赋值语句
StudentDO studentDO = new StudentDO();
BeanCopier beanCopier = BeanCopier.create(StudentDTO.class, StudentDO.class, false);
beanCopier.copy(studentDTO, studentDO, null);

BeanCopier is a little bit more than BeanUtils Compare studentDO with studentDTO objects:

 6 object replication tool classes, how to choose?8

From above, you can draw a conclusion that is broadly consistent with Spring Beanutils:

  • The field names are inconsistent and the property cannot be copied
  • The type is inconsistent and the property cannot be copied. ed.
  • Nested object fields will use the same object as the source object, i.e. shallow copies

Above we use beanutils, encountering this field name, type inconsistent situation, we have no good way, can only hand-written hard-coded.

Under BeanCopier, however, we can introduce converters for type conversion.

// 注意最后一个属性设置为 true
BeanCopier beanCopier = BeanCopier.create(StudentDTO.class, StudentDO.class, true);
// 自定义转换器
beanCopier.copy(studentDTO, studentDO, new Converter() {
    @Override
    public Object convert(Object source, Class target, Object context) {
        if (source instanceof Integer) {
            Integer num = (Integer) source;
            return num.toString();
        }
        return null;
    }
});

But spit this converter, and once we turn it on ourselves, all property replication needs us to come. F or example, in the example above, we only deal with when the source object field type is Integer, and in this case, nothing else is handled. We get that the DO object will only be copied by the name property.

 6 object replication tool classes, how to choose?9

Cglib BeanCopier works differently from the two beanutils above, dynamically generating a proxy class using bytecode technology, which implements the get and set methods. There is some overhead associated with building proxy classes, but once built, we can cache and reuse them, and all Cglib performance is better than the two beanutils above.

Dozer

Dozer, Chinese translated directly as a digger, is a "heavyweight" property copy tool class that has many powerful features compared to the three tool classes described above.

Voiceover: Heavyweight/lightweight is actually a relative statement, and since Dozer has many advanced features relative to tool classes like BeanUtils, it is a heavyweight tool class.

As soon as the editor came across this tool class, he was deeply impressed, really too powerful, above we expected the function, Dozer has given you to achieve.

Let's look at how to use it, and first we introduce Dozer dependencies:

<dependency>
  <groupId>net.sf.dozer</groupId>
  <artifactId>dozer</artifactId>
  <version>5.4.0</version>
</dependency>

Here's how to use it:

// 省略属性的代码
DozerBeanMapper mapper = new DozerBeanMapper();
StudentDO studentDO =
        mapper.map(studentDTO, StudentDO.class);
System.out.println(studentDO);

Dozer needs us to create a new DozerBeanMapper which acts like BeanUtils, responsible for mapping objects, and copying properties.

Voiceover: As we can see in the following code, generating DozerBeanMapper instance requires loading a profile, which is more expensive to generate at will. In our application, DozerBeanMapper should be reused using the single-case mode.

If the properties are all simple basic types, we can quickly complete the property replication by using the code above.

Unfortunately, we have string and Date type conversions in our code, and if we use the code above directly, the program will throw an exception.

 6 object replication tool classes, how to choose?10

So here we're going to use Dozer's powerful configuration capabilities in three ways:

  • XML
  • API
  • note

Among them, the API is cumbersome in a cumbersome way, most of which is currently done with XML, and the annotation feature is new features added after Dozer 5.3.2, although the functionality is weaker than XML.

How XML is used

Let's configure the DTO-DO relationship using XML configuration, starting with a new dozer/dozer-mapping.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://dozer.sourceforge.net
          http://dozer.sourceforge.net/schema/beanmapping.xsd">
    <!-- 类级别的日期转换,默认使用这个格式转换    -->
    <mapping date-format="yyyy-MM-dd HH:mm:ss">
        <class-a>com.just.doone.example.domain.StudentDTO</class-a>
        <class-b>com.just.doone.example.domain.StudentDO</class-b>
        <!-- 在下面指定字段名不一致的映射关系       -->
        <field>
            <a>no</a>
            <b>number</b>
        </field>


        <field>
            <!-- 字段级别的日期转换,将会覆盖字段上的转换            -->
            <a date-format="yy-MM-dd">createDate</a>
            <b>createDate</b>
        </field>
    </mapping>
</mappings>

Then modify our Java code to add a profile to read Dozer:

DozerBeanMapper mapper = new DozerBeanMapper();
List<String> mappingFiles = new ArrayList<>();
// 读取配置文件
mappingFiles.add("dozer/dozer-mapping.xml");
mapper.setMappingFiles(mappingFiles);
StudentDO studentDO = mapper.map(studentDTO, StudentDO.class);
System.out.println(studentDO);

After running, compare studentDO with the studentDTO object:

 6 object replication tool classes, how to choose?11

From the figure above, we can find:

  • Fields with inconsistent types, properties are copied
  • The DO and DTO object fields are not the same object, which is a deep copy
  • By configuring the mapping relationship of field names, properties of different fields are also copied

In addition to these relatively simple properties, Dozer supports many additional features, such as enumerating property replication, collection property replication such as Map, and so on.

 6 object replication tool classes, how to choose?12

Some small partners who have just seen dozer's usage may find this tool class cumbersome, unlike the BeanUtils tool class, where a line of code can be solved.

In fact, Dozer can be well integrated with the Spring framework, we can configure it in advance in the Spring profile, and then we just refer to dozer's corresponding bean and use it in one line of code.

Dozer integrates with Spring, and we can use its DozerBeanMapperFactoryBean configured as follows:

    <bean class="org.dozer.spring.DozerBeanMapperFactoryBean">
        <property name="mappingFiles" 
                  value="classpath*:/*mapping.xml"/>
      <!--自定义转换器-->
        <property name="customConverters">
            <list>
                <bean class=
                      "org.dozer.converters.CustomConverter"/>      
            </list>
        </property>
    </bean>

DozerBeanMapperFactoryBean supports a wide variety of settings properties, allowing you to customize the set type conversion and set other properties.

There is also an easy way to configure DozerBeanMapper in XML:

    <bean id="org.dozer.Mapper" class="org.dozer.DozerBeanMapper">
        <property name="mappingFiles">
            <list>
                <value>dozer/dozer-Mapperpping.xml</value>
            </list>
        </property>
    </bean>

Once the Spring configuration is complete, we can inject it directly into the code:

@Autowired
Mapper mapper;


public void objMapping(StudentDTO studentDTO) {
// 直接使用
StudentDO studentDO =
mapper.map(studentDTO, StudentDO.class);
}

How to annotate

Dozer annotations are weak compared to XML configurations and can only be mapped with inconsistent field names.

In the code above, we can use @Mapping annotations on the no field of the DTO so that when we complete the conversion with Dozer, the field properties are copied.

@Data
public class StudentDTO {


    private String name;


    private Integer age;
    @Mapping("number")
    private String no;


    private List<String> subjects;


    private Course course;
    private String createDate;
}

Although annotations are currently a bit weak, a later look at the version official may add new annotation features, and XML can be used with annotations.

Finally, the Dozer underlying layer essentially uses reflection to complete the copy of the property, so the execution speed is not ideal.

orika

Orika is also a heavyweight attribute replication tool class similar to Dozer and also provides features similar to Dozer." But orika doesn't need to use tedious XML configurations, and it provides a very concise set of API usages that are easy to get started.

First we introduce its latest dependencies:

<dependency>
    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.5.4</version>
</dependency>

Here's how to use it:

// 省略其他设值代码


// 这里先不要设值时间
// studentDTO.setCreateDate("2020-08-08");


MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper = mapperFactory.getMapperFacade();
StudentDO studentDO = mapper.map(studentDTO, StudentDO.class);

Here we introduce two MapperFactory and MapperFacade where MapperFactory can be used for field mapping, configuration converters, and so on, and MapperFacade is used as much as Beanutils to map between objects.

In the code above, we deliberately commented on the setting of the createDate time property in the DTO object because by default if there is no converter that sets the time type individually, the code above will be thrown wrong.

In addition, in the code above, properties with inconsistent field names are not copied, so we need to set them separately.

Let's set up a time converter and specify the field name:

MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
ConverterFactory converterFactory = mapperFactory.getConverterFactory();
converterFactory.registerConverter(new DateToStringConverter("yyyy-MM-dd"));
mapperFactory.classMap(StudentDTO.class, StudentDO.class)
        .field("no", "number")
      // 一定要调用下 byDefault
        .byDefault()
        .register();
MapperFacade mapper = mapperFactory.getMapperFacade();
StudentDO studentDO = mapper.map(studentDTO, StudentDO.class);

In the code above, first we need to register a time-type converter in ConverterFactory and second, we need MapperFactory to specify the mapping relationship between different field names.

Here we should note that after we use classMap if you want the same field name property to be copied by default, we must call byDefault method.

A simple comparison between DTO and DO objects:

 6 object replication tool classes, how to choose?13

Some of the features of orika can be found in the figure above:

  • The default support type is inconsistent (basic type/wrapper type) conversion
  • Deep copy is supported
  • Specify different field name mapping relationships, and properties can be successfully copied.

Orika also supports collection mapping:

MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
List<Person> persons = new ArrayList<>();
List<PersonDto> personDtos = mapperFactory.getMapperFacade().mapAsList(persons, PersonDto.class);

Finally, talk about the orika implementation principle, orika and dozer the underlying principle is not the same, the underlying it uses javassist to generate a field property mapping bytecode, and then directly dynamically load the execution bytecode file, compared to Dozer's use of reflection of the original tool class, much faster.

 6 object replication tool classes, how to choose?14

MapStruct

Unknowingly, in one breath has written 5 attribute replication tool classes, small partners see here, then don't give up, insist on reading, the following will introduce a tool class with the above are not quite the same as "MapStruct."

The tool classes described above, whether using reflection or bytecode techniques, need to be executed dynamically during code run, so they perform much slower than handwritten hard-coded methods.

Is there a tool class that runs at about the same speed as hard-coded?

This introduces mapStruct, a tool class that runs at about the same speed as hard-coded because it generates code for Java Bean property replication during compilation, which ensures high performance without the need for reflection or bytecode techniques.

In addition, because the code is generated during compilation, if there are any problems, the compilation period can be exposed in advance, which allows developers to solve the problem ahead of time, rather than waiting for the code application to go live before running to find the error.

Let's look at how to use this tool class, and first we introduce this dependency:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.3.1.Final</version>
</dependency>

Second, because MapStruct needs to generate code during the compiler, we need to configure it in the maven-compiler-plugin plug-in:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <source>1.8</source> <!-- depending on your project -->
        <target>1.8</target> <!-- depending on your project -->
        <annotationProcessorPaths>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.3.1.Final</version>
            </path>
            <!-- other annotation processors -->
        </annotationProcessorPaths>
    </configuration>
</plugin>

Next we need to define the mapping interface, the code is as follows:

@Mapper
public interface StudentMapper {


    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

  
    @Mapping(source = "no", target = "number")
    @Mapping(source = "createDate", target = "createDate", dateFormat = "yyyy-MM-dd")
    StudentDO dtoToDo(StudentDTO studentDTO);
}

We need to define a transformation interface @Mapper mapStruct annotation @Mapper so that once defined, StudentMapper functions like tool classes such as BeanUtils.

Second, because our DTO is inconsistent with the field name in the DO object, we are still specifying field maps using @Mapping annotations on the conversion method. In addition, we don't have the same type of createDate field, and here we need to specify the time formatting type.

Once the above definition is complete, we can use a one-line of StudentMapper code directly to complete the object transformation.

// 忽略其他代码
StudentDO studentDO = StudentMapper.INSTANCE.dtoToDo(studentDTO);

If our object uses Lombok, specify a different field name @Mapping and the following errors may be thrown during compilation:

 6 object replication tool classes, how to choose?15

This is mainly because Lombok also needs to generate code automatically during compilation, which can lead to a conflict between the two, and when MapStruct generates code, there is no Lombok generated code.

The solution is to include Lombok in the maven-compiler-plugin configuration as follows:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <source>1.8</source> <!-- depending on your project -->
        <target>1.8</target> <!-- depending on your project -->
        <annotationProcessorPaths>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.3.1.Final</version>
            </path>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.12</version>
            </path>
            <!-- other annotation processors -->
        </annotationProcessorPaths>
    </configuration>
</plugin>

The output DO and DTO are as follows:

 6 object replication tool classes, how to choose?16

From the image above, we can draw some conclusions:

  • Some types are inconsistent and can be converted automatically, for example

    • The basic type and the packaging type
    • The basic type of packaging type is with String

  • Deep copy

The examples described above describe some simple field mappings, and if a small partner encounters a total of other scenarios at work, you can first look at the project and see if there is an end solution: github.com/mapstruct/mapstruct-examples

We already know above that MapStruct generated code during compilation, so let's look at automatically generating code:

public class StudentMapperImpl implements StudentMapper {
    public StudentMapperImpl() {
    }


    public StudentDO dtoToDo(StudentDTO studentDTO) {
        if (studentDTO == null) {
            return null;
        } else {
            StudentDO studentDO = new StudentDO();
            studentDO.setNumber(studentDTO.getNo());


            try {
                if (studentDTO.getCreateDate() != null) {
                    studentDO.setCreateDate((new SimpleDateFormat("yyyy-MM-dd")).parse(studentDTO.getCreateDate()));
                }
            } catch (ParseException var4) {
                throw new RuntimeException(var4);
            }


            studentDO.setName(studentDTO.getName());
            if (studentDTO.getAge() != null) {
                studentDO.setAge(String.valueOf(studentDTO.getAge()));
            }


            List<String> list = studentDTO.getSubjects();
            if (list != null) {
                studentDO.setSubjects(new ArrayList(list));
            }


            studentDO.setCourse(studentDTO.getCourse());
            return studentDO;
        }
    }
}

From the generated code, there is no black magic, MapStruct automatically generates an implementation class StudentMapperImpl which implements dtoToDo which getter/setter settings.

As you can see from this, MapStruct does the equivalent of helping us write getter/setter so it performs well.

(Recommended micro-class: Java micro-class)

summary

Reading this article, we've learned a total of 7 property replication tool classes, so many tool classes how do we choose that? The editor-in-chief talks about some of his insights:

First, first of all, we directly abandon Apache Beanutils, this needless to say, Alibaba norms are set, we do not use it.

Second, of course, look at the performance of tool classes, the performance of these tool classes, online articles introduced more, small editors on replication, we can compare.

 6 object replication tool classes, how to choose?17

 6 object replication tool classes, how to choose?18

You can see that MapStruct's performance is still pretty good. So if your business is demanding performance, responsiveness, and so on, or if your business has a big data volume import/export scenario where the code has object conversions, don't use the Apache Beanutils, Dozer tool classes.

Third, in fact, a large part of the application is not very high performance requirements, as long as the tool class can provide sufficient convenience, it can be accepted. If you don't have complex requirements in your business, it's fine to use Spring Beanutils directly, since most of Spring's packages are used and we don't need to import other packages.

So if there are different types of business, different field names, consider using heavyweight tool classes like orika.

Source: Public Number - Java Geek Technology Author: Duck Blood Fans

That's W3Cschool编程狮 can choose from about 6 object replication tool classes? Related to the introduction, I hope to help you.