BeanCopier, BeanUtils object attribute copy

In order to isolate changes, development often isolates the DO queried by DAO from the DTO provided to the front end, and their structures are similar. Writing a lot of lengthy code like b.setFiled(a.getFiled()) is cumbersome and meaningless. So it is necessary to simplify the object copy method. Most of the time, Apache or Spring's BeanUtils are used, and there is another more efficient way to copy properties: BeanCopier.

1. Background

1️⃣ Object copy concept
Java, data types are divided into value types (basic data types) and reference types. Object copy is divided into shallow copy (shallow clone) and deep copy (deep clone) .

Difference between shallow copy and deep copy

2️⃣ Preparation before the example. Source object attribute class UserDO.class (the following examples, all source objects use this)

@Data
public class UserDO {
    
        
  private int id;    
  private String userName;    
  private LocalDateTime gmtBroth;    
  private BigDecimal balance;    
  public UserDO(Integer id, String userName, LocalDateTime gmtBroth, BigDecimal balance) {
    
            
    this.id = id;        
    this.userName = userName;        
    this.gmtBroth = gmtBroth;        
    this.balance = balance;    
  }
}

Create data tool class DataUtil

public class DataUtil {
    
        
  /**    
     * 模拟查询出一条数据     
      * @return     
      */    
  public static UserDO createData() {
    
            
    return new UserDO(1, "Van", LocalDateTime.now(),new BigDecimal(100L));    
  }    
  /**       
      * 模拟查询出多条数据     
      * @param num 数量     
      * @return     
      */    
  public static List<UserDO> createDataList(int num) {
    
            
    List<UserDO> userDOS = new ArrayList<>();        
    for (int i = 0; i < num; i++) {
    
                
    UserDO userDO = new UserDO(i+1, "Van", LocalDateTime.now(),new BigDecimal(100L));            
    userDOS.add(userDO);        
  }        
    return userDOS;    
 }
}

2. BeanUtils for object copy

Both Apache and Spring have BeanUtils tool classes. Apache's BeanUtils is not stable and efficient; Spring's BeanUtils is relatively stable and will not increase significantly in time due to large quantities, so Spring's BeanUtils is generally used.

1️⃣ Source code interpretation
of BeanUtils in Spring, the implementation method is very simple, that is, simply get/set the attributes with the same name in the two objects, and only check the accessibility of the attributes. Member variable assignment is based on the member list of the target object, and will skip ignore and those that do not exist in the source object, so this method is safe and will not cause errors due to structural differences between the two objects, but it must be guaranteed Two member variables with the same name are of the same type.

2️⃣ Example

@Slf4j
public class BeanUtilsDemo {
    
        
  public static void main(String[] args) {
    
            
    long start = System.currentTimeMillis();        
    UserDO userDO = DataUtil.createData();        
    log.info("拷贝前,userDO:{}", userDO);        
    UserDTO userDTO = new UserDTO();
    BeanUtils.copyProperties(userDO,userDTO);        
    log.info("拷贝后,userDO:{}", userDO);
  long end = System.currentTimeMillis();  
   }
}

result

18:12:11.734 [main] INFO cn.van.parameter.bean.copy.demo.BeanUtilsDemo 
- 拷贝前,userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T18:12:11.730, balance=100)
18:12:11.917 [main] INFO cn.van.parameter.bean.copy.demo.BeanUtilsDemo 
- 拷贝后,userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T18:12:11.730, balance=100)

3. BeanCopier for object copy

BeanCopier is used to copy properties between two beans. BeanCopier supports two methods:
1️⃣One is to not use Converter, only to copy variables with the same property name and type between two beans;
2️⃣The other is to introduce Converter, which can perform special operations on certain specific property values .

basic use

  • rely
<dependency>    
<groupId>cglib</groupId>    
<artifactId>cglib-nodep</artifactId>    
<version>3.3.0</version>
</dependency>

Note: This dependency is not necessary, because cglib has been integrated in Spring , and this article uses org.springframework.cglib.beans.BeanCopier.

3.1.1 The attribute names and types are the same

  • target object attribute class
@Data
public class UserDTO {
    
        
  private int id;    
  private String userName;
}
  • Test Methods
/** * 属性名称、类型都相同(部分属性不拷贝) */
private static void normalCopy() {
    
    	
// 模拟查询出数据    
UserDO userDO = DataUtil.createData();    
log.info("拷贝前,userDO:{}", userDO);    
//第一个参数:源对象。
//第二个参数:目标对象。
//第三个参数:是否使用自定义转换器(下面会介绍),下同    
BeanCopier b = BeanCopier.create(UserDO.class, UserDTO.class, false);    
UserDTO userDTO = new UserDTO();    
b.copy(userDO, userDTO, null);    
log.info("拷贝后,userDTO:{}", userDTO);
}
  • Result: copy successfully
18:24:24.080 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo 
- 拷贝前,userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T18:24:24.077, balance=100)
18:24:24.200 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo 
- 拷贝后,userDTO:UserDTO(id=1, userName=Van)

3.1.2 Same property name but different type

  • target object attribute class
@Data
public class UserEntity {
    
        
  private Integer id;    
  private String userName;
}
  • Test Methods
/** * 属性名称相同、类型不同 */
private static void sameNameDifferentType() {
    
        
// 模拟查询出数据    
UserDO userDO = DataUtil.createData();    
log.info("拷贝前,userDO:{}", userDO);    
BeanCopier b = BeanCopier.create(UserDO.class, UserEntity.class, false);
UserEntity userEntity = new UserEntity();    
b.copy(userDO, userEntity, null);    
log.info("拷贝后,userEntity:{}", userEntity);}
  • result
19:43:31.645 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo 
- 拷贝前,userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T19:43:31.642, balance=100)
19:43:31.748 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo 
- 拷贝后,userEntity:UserEntity(id=null, userName=Van)
  • Analysis
    Through the log, it can be found that the int type id of UserDO cannot be copied to the Integer id of UserEntity.

3.1.3 Section

BeanCopier only copies properties with the same name and type.

Even if the source type is a primitive type (int, short, char, etc.), the target type is its wrapper type (Integer, Short, Character, etc.), or vice versa: it will not be copied.

3.2 Custom Converter

According to 3.1.2, when the attribute types of the source and target classes are different, the attribute cannot be copied. At this time, we can customize the converter by implementing the Converter interface

3.2.1 Preparation

  • target object attribute class
@Data
public class UserDomain {
    
        
  private Integer id;    
  private String userName;        
/**     
    * 以下两个字段用户模拟自定义转换    
    */    
  private String gmtBroth;    
  private String balance;
}

3.2.2 Without Converter

  • Test Methods
/** * 类型不同,不使用Converter */
public static void noConverterTest() {
    
        
  // 模拟查询出数据    
  UserDO userDO = DataUtil.createData();   
  log.info("拷贝前,userDO:{}", userDO);    
  BeanCopier copier = BeanCopier.create(UserDO.class, UserDomain.class, false);
  UserDomain userDomain = new UserDomain();    
  copier.copy(userDO, userDomain, null);    
  log.info("拷贝后,userDomain:{}", userDomain);}
  • result
19:49:19.294 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo 
- 拷贝前,userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T19:49:19.290, balance=100)
19:49:19.394 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo 
- 拷贝后,userDomain:UserDomain(id=null, userName=Van, gmtBroth=null, balance=null)
  • Analysis
    Through the comparison of the printed logs before and after, the field id, gmtBroth, and balance with different attribute types have not been copied.

3.2.3 Using Converter

  • Implement the Converter interface to customize attribute conversion
public  class UserConverter implements Converter {
    
        
   /**    
     * 时间转换的格式    
     */    
  DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");    
  /**    
    * 自定义属性转换    
    * @param value 源对象属性类     
    * @param target 目标对象里属性对应set方法名,eg.setId     
    * @param context 目标对象属性类   
    * @return     
    */    
  @Override
  public Object convert(Object value, Class target, Object context) {
    
         
     if (value instanceof Integer) {
    
                
        return value;      
     } else if (value instanceof LocalDateTime) {
    
          
        LocalDateTime date = (LocalDateTime) value;      
        return dtf.format(date);     
     } else if (value instanceof BigDecimal) {
    
                
        BigDecimal bd = (BigDecimal) value;       
        return bd.toPlainString();      
     }       
   return value;  
  }
}
  • Test Methods
/** 
  * 类型不同,使用Converter 
  */
public static void converterTest() {
    
        
  // 模拟查询出数据    
  UserDO userDO = DataUtil.createData();    
  log.info("拷贝前,userDO:{}", userDO);    
  BeanCopier copier = BeanCopier.create(UserDO.class, UserDomain.class, true);
  UserConverter converter = new UserConverter();    
  UserDomain userDomain = new UserDomain();    
  copier.copy(userDO, userDomain, converter);    
  log.info("拷贝后:userDomain:{}", userDomain);
}
  • Result: copy all
19:51:11.989 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopierDemo 
- 拷贝前,userDO:UserDO(id=1, userName=Van, gmtBroth=2019-11-02T19:51:11.985, balance=100)

3.2.4 Section

  1. Once Converter is used, BeanCopier only uses the rules defined by Converter to copy properties, so all properties should be considered in the convert() method.
  2. But using Converter will slow down object copying.

3.3 BeanCopier summary

  1. When the attribute names and types of the source and target classes are the same, copying is fine.
  2. When the attributes of the source object and the target object have the same name but different types, the attributes with the same name but different types will not be copied. Note that the primitive types (int, short, char) and their wrapper types are treated as different types here, so they will not be copied.
  3. The source class or target class has fewer setters than getters, and copying is no problem. At this time, the setters are redundant, but no error will be reported.
  4. The source class and the target class have the same properties (the getters of both exist), but the setter of the target class does not exist, and a NullPointerException will be thrown at this time.

4. Speed ​​comparison between BeanUtils and BeanCopier

4.1 BeanUtils

  • test code
private static void beanUtil() {
    
        
  List<UserDO> list = DataUtil.createDataList(10000);    
  long start = System.currentTimeMillis();    
  List<UserDTO> dtoList = new ArrayList<>();    
  list.forEach(userDO -> {
    
      UserDTO userDTO = new UserDTO();
  BeanUtils.copyProperties(userDO,userDTO);
  dtoList.add(userDTO);    
  });    
  log.info("BeanUtils cotTime: {}ms", System.currentTimeMillis() - start);
}
  • Result (time-consuming: 232ms)
20:14:24.380 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopyComparedDemo 
- BeanUtils cotTime: 232ms

4.2 BeanCopier

  • test code
private static void beanCopier() {
    
        
  // 工具类生成10w条数据    
  List<UserDO> doList = DataUtil.createDataList(10000);    
  long start = System.currentTimeMillis();    
  List<UserDTO> dtoList = new ArrayList<>();    
  doList.forEach(userDO -> {
    
            
  BeanCopier b = BeanCopier.create(UserDO.class, UserDTO.class, false);
  UserDTO userDTO = new UserDTO();        
  b.copy(userDO, userDTO, null);        
  dtoList.add(userDTO);    });    
  log.info("BeanCopier costTime: {}ms", System.currentTimeMillis() - start);
}
  • Result (time-consuming: 116ms)
20:15:24.380 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopyComparedDemo 
- BeanCopier costTime: 116ms

4.3 Cache BeanCopier instance to improve performance

BeanCopier copying speed is fast, and the performance bottleneck appears in the process of creating BeanCopier instance. Therefore, put the created BeanCopier instance in the cache, and it can be obtained directly next time to improve performance.

  • test code
private static void beanCopierWithCache() {
    
        
    List<UserDO> userDOList = DataUtil.createDataList(10000);    
    long start = System.currentTimeMillis();    
    List<UserDTO> userDTOS = new ArrayList<>();    
    userDOList.forEach(userDO -> {
    
            
    UserDTO userDTO = new UserDTO();        
    copy(userDO, userDTO);        
    userDTOS.add(userDTO);    
    });    
    log.info("BeanCopier 加缓存后 costTime: {}ms", System.currentTimeMillis() - start);}
public static void copy(Object srcObj, Object destObj) {
    
        
    String key = genKey(srcObj.getClass(), destObj.getClass());    
    BeanCopier copier = null;    
    if (!BEAN_COPIERS.containsKey(key)) {
    
            
      copier = BeanCopier.create(srcObj.getClass(), destObj.getClass(), false);
      BEAN_COPIERS.put(key, copier);    
    } else {
    
            
      copier = BEAN_COPIERS.get(key);    
    }    
    copier.copy(srcObj, destObj, null);
}
private static String genKey(Class<?> srcClazz, Class<?> destClazz) {
    
        
    return srcClazz.getName() + destClazz.getName();
}
  • Result (time-consuming: 6ms)
20:32:31.405 [main] INFO cn.van.parameter.bean.copy.demo.BeanCopyComparedDemo 
- BeanCopier 加缓存后 costTime: 6ms

5. Summary and source code

Guess you like

Origin blog.csdn.net/ChineseSoftware/article/details/123814375