When doing business, we isolate the DO queried by the DAO from the DTOS provided to the front end in order to isolate changes. About 90% of the time, they’re all structurally similar; But we don’t like writing a lot of long b.setf1 (a.getf1 ()) code, so we need to simplify object copying.
Most of the time you use Apache or Spring BeanUtils. Today, we’ll look at a more efficient way to copy properties: BeanCopier.
The background,
1.1 Concept of Object Copy
In Java, data types are divided into value types (basic data types) and reference types. Value types include simple data types such as INT, double, Byte, Boolean, char, and reference types include complex types such as classes, interfaces, and arrays.
Object copy is classified into shallow copy (shallow clone) and deep copy (deep clone).
- Difference between shallow copy and deep copy
classification | Shallow copy | Deep copy |
---|---|---|
The difference between | Create a new object and copy the non-static fields of the current object into the new object, or if the fields are of value type. Copy if the field is a reference typeObjects referenced but not copied. Therefore, the original object and its copy refer to the same object. | Create a new object and copy the non-static fields of the current object into the new object, regardless of whether the fields are of value type or reference typeMake a separate copy. When you modify anything in one object, it doesn’t affect anything in the other. |
Refer to the article
1.2 Example Preparations
- Source object property class
UserDO.class
(In the following example, this is used for all source objects)
@Data
public class UserDO {
private int id;
private String userName;
/** * The following two fields are user simulated custom conversions */
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; }}Copy the code
- Build data utility classes
DataUtil.class
public class DataUtil {
/** * query a data *@return* /
public static UserDO createData(a) {
return new UserDO(1."Van", LocalDateTime.now(),new BigDecimal(100L));
}
/** * select * from *@paramNum number *@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);
}
returnuserDOS; }}Copy the code
Object copy BeanUtils
Apache and Spring both have the BeanUtils utility class, Apache’s BeanUtils stability and efficiency are not good; Spring’s BeanUtils is relatively stable and will not increase significantly due to a large amount of time. Therefore, Spring’s BeanUtils is generally used.
2.1 Source code Interpretation
BeanUtils in Spring, which is implemented in a very simple way, is a simple get/set on two objects of the same name attribute, only check the accessibility of the attribute.
BeanUtils source
As you can see, member variable assignments are based on the member list of the target object, and ignore and non-existent ones are skipped, so this method is safe and does not cause errors due to structural differences between the two objects, but two member variables with the same name must be of the same type.
2.2 the sample
@Slf4j
public class BeanUtilsDemo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
UserDO userDO = DataUtil.createData();
log.info(Before copy,userDO:{}, userDO);
UserDTO userDTO = new UserDTO();
BeanUtils.copyProperties(userDO,userDTO);
log.info("Copy,userDO:{}", userDO); }}Copy the code
- The results of
18:12:11. [the main] INFO cn 734. Van. Parameter. Beans. Copy. The demo. BeanUtilsDemo - copy before userDO: userDO (id = 1, the userName = van, GmtBroth T18: = 2019-11-02 and. 730, 18:12:11 balance = 100). 917. [the main] INFO cn. Van. Parameter. Beans. Copy. The demo. After BeanUtilsDemo - copy, userDO: userDO (id = 1, UserName = Van, gmtBroth T18: = 2019-11-02 and. 730, the balance = 100)Copy the code
BeanCopier for object copy
BeanCopier is used to copy properties between two beans. BeanCopier supports two ways:
- One is not to use it
Converter
The way, just for twobean
Copy variables with identical attribute names and types; - The other one is introduced
Converter
, you can perform special operations on specific attribute values.
3.1 Basic Usage
- Rely on
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.3.0</version>
</dependency>
Copy the code
Note: * * * * the dependence not must, because Spring has been integrated in additional, the blogger use is org. Springframework. Additional. Beans. BeanCopier.
3.1.1 Attribute name and type are the same
- Target object property class
@Data
public class UserDTO {
private int id;
private String userName;
}
Copy the code
- The test method
/** * All attributes have the same name and type (some attributes are not copied) */
private static void normalCopy(a) {
// simulate query data
UserDO userDO = DataUtil.createData();
log.info("Before copy: userDO:{}", userDO);
// The first parameter is the source object, the second parameter is the target object, and the third parameter is whether to use a custom converter (described below)
BeanCopier b = BeanCopier.create(UserDO.class, UserDTO.class, false);
UserDTO userDTO = new UserDTO();
b.copy(userDO, userDTO, null);
log.info("After copy: userDTO:{}", userDTO);
}
Copy the code
- Result: The copy is successful
18:24:24. [the main] INFO cn 080. Van. Parameter. Beans. Copy. The demo. BeanCopierDemo - copy before: UserDO: userDO (id = 1, the userName = Van, gmtBroth = 2019-11-02 T18: ". 077, 18:24:24 balance = 100). 200. [the main] INFO cn. Van. Parameter. Beans. Copy. The demo. BeanCopierDemo - copy after: userDTO:UserDTO(id=1, userName=Van)Copy the code
3.1.2 Attribute Names are the same but Types are different
- Target object property class
@Data
public class UserEntity {
private Integer id;
private String userName;
}
Copy the code
- The test method
/** * Attributes have the same name but different types */
private static void sameNameDifferentType(a) {
// simulate query data
UserDO userDO = DataUtil.createData();
log.info("Before copy: userDO:{}", userDO);
BeanCopier b = BeanCopier.create(UserDO.class, UserEntity.class, false);
UserEntity userEntity = new UserEntity();
b.copy(userDO, userEntity, null);
log.info("After copy: userEntity:{}", userEntity);
}
Copy the code
- The results of
19:43:31. [the main] INFO cn 645. Van. Parameter. Beans. Copy. The demo. BeanCopierDemo - copy before: UserDO: userDO (id = 1, the userName = Van, gmtBroth = 2019-11-02 T19:43:31. 642, 19:43:31 balance = 100). 748. [the main] INFO cn. Van. Parameter. Beans. Copy. The demo. BeanCopierDemo - copy after: userEntity:UserEntity(id=null, userName=Van)Copy the code
- Analysis of the
The log shows that the INT ID of UserDO cannot be copied to the Integer ID of UserEntity.
3.1.3 section
BeanCopier copies only properties with the same name and type.
Even if the source type is primitive (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 source and target class have different attribute types, the attribute cannot be copied. In this case, we can customize the Converter by implementing the Converter interface
3.2.1 preparation
- Target object property class
@Data
public class UserDomain {
private Integer id;
private String userName;
/** * The following two fields are user simulated custom conversions */
private String gmtBroth;
private String balance;
}
Copy the code
3.2.2 don’t useConverter
- The test method
/** * Different types do not use Converter */
public static void noConverterTest(a) {
// simulate query data
UserDO userDO = DataUtil.createData();
log.info("Before copy: userDO:{}", userDO);
BeanCopier copier = BeanCopier.create(UserDO.class, UserDomain.class, false);
UserDomain userDomain = new UserDomain();
copier.copy(userDO, userDomain, null);
log.info("After copy: userDomain:{}", userDomain);
}
Copy the code
- The results of
19:49:19. [the main] INFO cn 294. Van. Parameter. Beans. Copy. The demo. BeanCopierDemo - copy before: UserDO: userDO (id = 1, the userName = Van, gmtBroth = 2019-11-02 T19: surely. 290, Balance = 100). 19:49:19 394 [main] INFO cn. Van. Parameter. Beans. Copy. The demo. BeanCopierDemo - copy after: userDomain:UserDomain(id=null, userName=Van, gmtBroth=null, balance=null)Copy the code
- Analysis of the
The field ID,gmtBroth, and Balance with different attribute types were not copied by comparing the printed logs before and after.
3.2.3 useConverter
- implementation
Converter
Interface custom property transformation
public class UserConverter implements Converter {
/** * Time conversion format */
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/** * Custom attribute conversion *@paramValue Source object attribute class *@paramTarget Specifies the name of the set method in the target object,eg. SetId *@paramContext Target object property class *@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();
}
// More type conversions please customize
returnvalue; }}Copy the code
- The test method
/** * Use Converter */
public static void converterTest(a) {
// simulate query data
UserDO userDO = DataUtil.createData();
log.info("Before copy: 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("After copy: userDomain:{}", userDomain);
}
Copy the code
- Result: all copies
19:51:11. [the main] INFO cn 989. Van. Parameter. Beans. Copy. The demo. BeanCopierDemo - copy before: UserDO: userDO (id = 1, the userName = Van, gmtBroth = 2019-11-02 T19: therefore, 985, 19:51:12 balance = 100). 096. [the main] INFO cn. Van. Parameter. Beans. Copy. The demo. BeanCopierDemo - copy after: userDomain:UserDomain(id=1, userName=Van, gmtBroth=2019-11-02 19:51:11, balance=100)Copy the code
3.2.4 section
- Once the use
Converter
.BeanCopier
Use onlyConverter
Define rules to copy attributes, so inconvert()
All attributes are considered in the method. - But, the use of
Converter
The object copy speed slows down.
3.3 BeanCopier
conclusion
- When the source class and the target class have the same attribute name and type, copying is fine.
- If the source object and the target object have the same name but different types of attributes, the attributes with the same name but different types will not be copied. Note that the primitive type (
int
.short
.char
) and their package types are treated as different types here and therefore will not be copied. - Of the source or target class
setter
thangetter
Small, copy is ok, at this pointsetter
Redundant, but no error. - The source class and the target class have the same attributes
getter
Exists), but for the target classsetter
Does not exist, will be thrownNullPointerException
.
Fourth, BeanUtils and BeanCopier speed comparison
Without further ado, I will directly demonstrate the time comparison of 10000 data copies between the two tools
4.1 BeanUtils
- The test code
private static void beanUtil(a) {
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);
}
Copy the code
- Results (Time consuming:
232ms
)
20:14:24. [the main] INFO cn 380. Van. Parameter. Beans. Copy. Demo. BeanCopyComparedDemo - BeanUtils cotTime: 232 msCopy the code
4.2 BeanCopier
- The test code
private static void beanCopier(a) {
// The utility class generates 10W data
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);
}
Copy the code
- Results (Time consuming:
116ms
)
20:15:24. [the main] INFO cn 380. Van. Parameter. Beans. Copy. Demo. BeanCopyComparedDemo - BeanCopier costTime: 116 msCopy the code
4.3 the cacheBeanCopier
Instances improve performance
The BeanCopier copy speed is fast, and the performance bottleneck appears in the process of creating BeanCopier instances. So, put the created BeanCopier instances in the cache so they can be retrieved next time to improve performance.
- The test code
private static void beanCopierWithCache(a) {
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();
}
Copy the code
- Results (Time consuming:
6ms
)
20:32:31. [the main] INFO cn 405. Van. Parameter. Beans. Copy. Demo. BeanCopyComparedDemo - BeanCopier after add cache costTime: 6 msCopy the code
Five, summary and source code
scenario | Time (10000 calls) | The principle of |
---|---|---|
BeanUtils | 232ms | reflection |
BeanCopier | 116ms | Modify bytecode |
BeanCopier(plus cache) | 6ms | Modify bytecode |
Github sample code
Recommended: BeanCopier source code analysis
Technical communication
- Over the blog
- Travelogue Blog – Blog Garden