This article is participating in “Java Theme Month – Java Swipe Card”, see the activity link for details

In our daily work, we often need to copy or transform objects. For example, when passing parameters, we convert the DTO of the parameters into PO and store it in the database. When returning to the front end, we convert PO into VO. If the division is more detailed, there may be objects such as DO(Domain Object), TO(Transfer Object), BO(Business Object) and so on. With the division of business becoming more and more detailed, the copy of objects becomes more and more frequent. So in this article, I’ll take a look at the common object copying tools and their differences.

Common tools are as follows:

  • Apache BeanUtils
  • Spring BeanUtils
  • cglib BeanCopier
  • Hutool BeanUtil
  • Mapstruct
  • Dozer

To prepare, create two classes PO and DTO:

@Data
public class OrderPO {
    Integer id;
    String orderNumber;
    List<String> proId;
}

@Data
public class OrderDTO {
    int id;
    String orderNumber;
    List<String> proId;
}
Copy the code

1 、Apache BeanUtils

Introduce dependent coordinates:

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.93.</version>
</dependency>
Copy the code

To test, initialize the PO object, and create an empty DTO object, using BeanUtils:

@org.junit.Test
public void test(a){
    OrderPO orderPO=new OrderPO();
    orderPO.setId(1);
    orderPO.setOrderNumber("orderNumber");
    ArrayList<String> list = new ArrayList<String>() {{
        add("1");
        add("2");
    }};
    orderPO.setProId(list);

    OrderDTO orderDTO=new OrderDTO();
    BeanUtils.copyProperties(orderDTO,orderPO);
}
Copy the code

Prints two objects with the same properties:

OrderPO(id=1, orderNumber=orderNumber, proId=[1.2])
OrderDTO(id=1, orderNumber=orderNumber, proId=[1.2])
Copy the code

As you can see, when the properties in a Bean with the same name are the basic data type and the wrapper class, such as int and Integer, the copy works fine. So dig a little deeper, when copying beans, do you use a deep copy or a shallow copy?

The two List objects use the same object, so in the copy, if there is a reference object, the shallow copy is used. If you modify this object after the copy is complete:

list.add("3");
log.info(orderDTO.getProId());
Copy the code

Print the DTO object again and find that the changed value is added even if you do not copy it again

OrderDTO(id=1, orderNumber=orderNumber, proId=[1.2.3])
Copy the code

Spring BeanUtils

If you use a Spring project that does not need to import dependencies separately, you need to import coordinates separately:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.22.RELEASE</version> </dependency> ··· RELEASE</version> </dependency>  ```java BeanUtils.copyProperties(orderPO,orderDTO);Copy the code

Procedure omission, again using shallow copy. Spring’s BeanUtils also provides an additional method that can be copied without considering certain properties:

void copyProperties(Object source, Object target, String... ignoreProperties);
Copy the code

Ignore the orderNumber attribute to copy:

BeanUtils.copyProperties(orderPO,orderDTO,"orderNumber");
Copy the code

Output results:

OrderPO(id=1, orderNumber=orderNumber, proId=[1.2])
OrderDTO(id=1, orderNumber=null, proId=[1.2])
Copy the code

In addition, in alibaba’s development manual, it is mandatory to avoid copying with Apache BeanUtils, instead of Spring BeanUtils or BeanCopier as described below. The main reason for this is that Spring doesn’t do as much validation on reflection as Apache does, and Spring BeanUtils uses caching internally to speed up transformations. In addition, since most of our projects are already integrated with Spring, we can use its BeanUtils directly for our basic needs if there are no special needs.

3, additional BeanCopier

If the project contains spring-core dependencies, you do not need to introduce additional dependencies, otherwise you need to introduce coordinates:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3. 0</version>
</dependency>
Copy the code

Example:

BeanCopier beanCopier = BeanCopier.create(
          orderPO.getClass(), 
          orderDTO.getClass(), false);
beanCopier.copy(orderPO,orderDTO,null);
Copy the code

Test results:

OrderPO(id=1, orderNumber=orderNumber, proId=[1.2])
OrderDTO(id=0, orderNumber=orderNumber, proId=[1.2])
Copy the code

In the example above, the ID field is not copied properly. The difference between the two fields is that the wrapper type Integer is used in the PO, but the basic type INT is used in the DTO. Therefore, when using BeanCopier, if there are basic types and wrapper classes, they cannot be copied properly. Only when they are the same type can they be copied properly. In addition, BeanCopier is still using a shallow copy, so you can experiment with the verification process yourself.

4, Hutool BeanUtil

Hutool is a tool kit used frequently by individuals. It encapsulates JDK methods such as file, encryption and decryption, transcoding, re, thread, XML, and can also be used to copy objects. Introduce coordinates before use:

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.1. 0</version>
</dependency>
Copy the code

Use the following method, and use shallow copy mode:

BeanUtil.copyProperties(orderPO,orderDTO);
Copy the code

As with Spring BeanUtils, properties can also be ignored:

void copyProperties(Object source, Object target, String... ignoreProperties);
Copy the code

In addition, Hutool’s BeanUtil provides a number of other practical methods:

It is common to convert beans and maps to each other, and sometimes it is easier to convert a Map to a Bean later on when using a Map to receive parameters

5, Mapstruct

The use of Mapstruct is a little different from the above methods, because spring, Apache, hutool all use reflection, cglib is based on bytecode files, and both are executed dynamically while the code is running, but Mapstruct is different. It generates code that copies the Bean properties at compile time, eliminates the need to use reflection or bytecode techniques at run time, and thus has high performance.

Using a Mapstruct requires the introduction of the following dependencies:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-jdk8</artifactId>
    <version>1.3. 0.Final</version>
</dependency>
<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct-processor</artifactId>
    <version>1.3. 0.Final</version>
</dependency>
Copy the code

You need to write an additional interface to implement this:

@Mapper
public interface ConvertMapper {
    OrderDTO po2Dto(OrderPO orderPO);
}
Copy the code

The @mapper annotation here is not for myBatis, but for org.mapstruct.mapper. It’s also very simple to use:

ConvertMapper mapper = Mappers.getMapper(ConvertMapper.class);
OrderDTO orderDTO=mapper.po2Dto(orderPO);
Copy the code

The ConvertMapperImpl implementation class is generated from the ConvertMapper interface we defined at compile time, and the po2Dto method is implemented. Take a look at the compiled file:

You can see that the set method is generated for each property in the method, and for the reference object, a new object is generated, using a deep copy, so the value will not change if you modify the previous reference object. Also, this way of using set/get is much faster than using reflection.

6, Dozer

Dozer is a bean-to-bean mapper that recursively copies data from one object to another, and the beans can have different complex types. Introduce dependency coordinates before using:

<dependency>
  <groupId>net.sf.dozer</groupId>
  <artifactId>dozer</artifactId>
  <version>5.4. 0</version>
</dependency>
Copy the code

The invocation is very simple:

DozerBeanMapper mapper = new DozerBeanMapper();
OrderDTO orderDTO=mapper.map(orderPO,OrderDTO.class);
Copy the code

Looking at the runtime generated objects, you can see how deep copies are used:

In addition, you can configure the mapping of different attribute names, modify the DTO and PO, add a name attribute in the PO, and add a value attribute in the DTO:

@Data
public class OrderPO {
    Integer id;
    String orderNumber;
    List<String> proId;
    String name;
}
@Data
public class OrderDTO {
    int id;
    String orderNumber;
    List<String> proId;
    String value;
}
Copy the code

Create a configuration file and add the mapping between the fields in the configuration file.


      
<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>
        <class-a>com.cn.entity.OrderPO</class-a>
        <class-b>com.cn.entity.OrderDTO</class-b>
        <field>
            <a>name</a>
            <b>value</b>
        </field>
    </mapping>
</mappings>
Copy the code

DozerBeanMapper uses the configuration file above to copy the object again:

. orderPO.setName("hydra");

DozerBeanMapper mapper = new DozerBeanMapper();
List<String> mappingFiles = new ArrayList<>();
mappingFiles.add("dozer.xml");
mapper.setMappingFiles(mappingFiles);
OrderDTO orderDTO=mapper.map(orderPO,OrderDTO.class);
Copy the code

To view test results, different names of fields can also be copied:

OrderPO(id=1, orderNumber=orderNumber, proId=[1.2], name=hydra)
OrderDTO(id=1, orderNumber=orderNumber, proId=[1.2], value=hydra)
Copy the code

If the beans in the business scenario have many different properties, this can be cumbersome to configure, requiring many additional XML files to be written. The above is often contacted in the work of several object copy tools, in the specific use, more to combine the requirements of copy efficiency, as well as the need to use in the work scene is deep copy or shallow copy and many other factors.