In actual project development, the assignment between objects is common. As e-commerce processes such as Double Eleven and Seckill become more complex, the amount of data is also increasing, and efficiency problems have surfaced.
Q: If you were to write the code for assignment between objects, what would you do?
Answer: Don't even think about it, just start the code directly, just get and set.
Q: Like the picture below?
A: Yeah, how can you put my code online?
Q: No, I'm just giving an example
A: This involves commercial secrets and is a very serious issue
Q: I found that you are very good at talking, can you answer the question directly?
Answer: OK, OK, I also feel that writing like this is very low. After writing like this last time, I almost got beaten
- There are too many objects, ctrl c + strl v, the keyboard is almost broken;
- And it is easy to make mistakes, if you are not paying attention, the attribute does not correspond, and the wrong value is assigned;
- The code looks stupid, a class has thousands of lines, all get and set copies, and even gave it a very elegant name transfer;
- If the attribute name cannot be seen from the name, you have to add a comment on the meaning of each attribute (basically, this kind of assignment operation must be added, the comment is very important, the comment is very important, the comment is very important);
- The code is cumbersome to maintain;
- If there are too many objects, there will be a class explosion problem. If there are too many attributes, it will seriously violate the Alibaba code specification (the actual code of a method is at most 20 lines);
Question: Okay, okay, tell me how to solve it.
Answer: It's very simple, you can directly assign values through the tool class Beanutils
Question: I heard that tools are very popular recently, which one do you use?
Answer: Just Apache
the one that comes with you, it’s really simple. I wrote one by hand for your appreciation.
Question: Your code reports an error, avoid using Apache Beanutils to copy attributes.
Answer: No error, just a serious warning,代码能跑就行,有问题再优化呗
Q: What's your attitude? Why is there a serious warning for the person who is drawn in the personnel affairs?
Answer: How much money to take, how much work to do, I am not XXX, it should be a performance problem
Q: What is the specific reason?
Answer: For 3000 yuan, you have to tear up apache copyProperties
the source code by hand?
Called through the singleton mode copyProperties
, however, each method corresponds to an BeanUtilsBean.getInstance()
instance, and each class instance corresponds to an instance, which is not a real singleton mode.
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
Performance bottleneck --> Too many logs are also a problem
As you can see from the source code, each of them copyProperties
has to perform multiple type checks and print logs.
/**
* org.apache.commons.beanutils.BeanUtils.copyProperties方法源码解析
* @author 哪吒编程
* @time 2023-01-07
*/
public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
// 类型检查
if (dest == null) {
throw new IllegalArgumentException("No destination bean specified");
} else if (orig == null) {
throw new IllegalArgumentException("No origin bean specified");
} else {
// 打印日志
if (this.log.isDebugEnabled()) {
this.log.debug("BeanUtils.copyProperties(" + dest + ", " + orig + ")");
}
int var5;
int var6;
String name;
Object value;
// 类型检查
// DanyBean 提供了可以动态修改实现他的类的属性名称、属性值、属性类型的功能
if (orig instanceof DynaBean) {
// 获取源对象所有属性
DynaProperty[] origDescriptors = ((DynaBean)orig).getDynaClass().getDynaProperties();
DynaProperty[] var4 = origDescriptors;
var5 = origDescriptors.length;
for(var6 = 0; var6 < var5; ++var6) {
DynaProperty origDescriptor = var4[var6];
// 获取源对象属性名
name = origDescriptor.getName();
// 判断源对象是否可读、判断目标对象是否可写
if (this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {
// 获取对应的值
value = ((DynaBean)orig).get(name);
// 每个属性都调用一次copyProperty
this.copyProperty(dest, name, value);
}
}
} else if (orig instanceof Map) {
Map<String, Object> propMap = (Map)orig;
Iterator var13 = propMap.entrySet().iterator();
while(var13.hasNext()) {
Map.Entry<String, Object> entry = (Map.Entry)var13.next();
String name = (String)entry.getKey();
if (this.getPropertyUtils().isWriteable(dest, name)) {
this.copyProperty(dest, name, entry.getValue());
}
}
} else {
PropertyDescriptor[] origDescriptors = this.getPropertyUtils().getPropertyDescriptors(orig);
PropertyDescriptor[] var14 = origDescriptors;
var5 = origDescriptors.length;
for(var6 = 0; var6 < var5; ++var6) {
PropertyDescriptor origDescriptor = var14[var6];
name = origDescriptor.getName();
if (!"class".equals(name) && this.getPropertyUtils().isReadable(orig, name) && this.getPropertyUtils().isWriteable(dest, name)) {
try {
value = this.getPropertyUtils().getSimpleProperty(orig, name);
this.copyProperty(dest, name, value);
} catch (NoSuchMethodException var10) {
}
}
}
}
}
}
Detect code performance through jvisualvm.exe
Then jvisualvm.exe
check the operation status, and sure enough, it is logging.log4j
impressively listed, ranking top 1 in time-consuming.
Q: Is there any other good way? better performance
Answer: Of course there are. As far as I know, there are 4 types of tools. In fact, there may be more. Without further ado, let me briefly introduce them first.
- org.apache.commons.beanutils.BeanUtils;
- org.apache.commons.beanutils.PropertyUtils;
- org.springframework.cglib.beans.BeanCopier;
- org.springframework.beans.BeanUtils;
Q: Then why don't you use it?
Answer: OK, let me demonstrate
package com.nezha.copy;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.util.StopWatch;
public class Test {
public static void main(String[] args) {
User user = new User();
user.setUserId("1");
user.setUserName("哪吒编程");
user.setCardId("123");
user.setCreateTime("2023-01-03");
user.setEmail("[email protected]");
user.setOperate("哪吒");
user.setOrgId("46987916");
user.setPassword("123456");
user.setPhone("10086");
user.setRemark("456");
user.setSex(1);
user.setStatus("1");
user.setTel("110");
user.setType("0");
user.setUpdateTime("2023-01-05");
User target = new User();
int sum = 10000000;
apacheBeanUtilsCopyTest(user,target,sum);
commonsPropertyCopyTest(user,target,sum);
cglibBeanCopyTest(user,target,sum);
springBeanCopyTest(user,target,sum);
}
private static void apacheBeanUtilsCopyTest(User source, User target, int sum) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < sum; i++) {
apacheBeanUtilsCopy(source,target);
}
stopWatch.stop();
System.out.println("使用org.apache.commons.beanutils.BeanUtils方式赋值"+sum+"个user对象,耗时:"+stopWatch.getLastTaskTimeMillis()+"毫秒");
}
/**
* org.apache.commons.beanutils.BeanUtils方式
*/
private static void apacheBeanUtilsCopy(User source, User target) {
try {
BeanUtils.copyProperties(source, target);
} catch (Exception e) {
}
}
private static void commonsPropertyCopyTest(User source, User target, int sum) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < sum; i++) {
commonsPropertyCopy(source,target);
}
stopWatch.stop();
System.out.println("使用org.apache.commons.beanutils.PropertyUtils方式赋值"+sum+"个user对象,耗时:"+stopWatch.getLastTaskTimeMillis()+"毫秒");
}
/**
* org.apache.commons.beanutils.PropertyUtils方式
*/
private static void commonsPropertyCopy(User source, User target) {
try {
PropertyUtils.copyProperties(target, source);
} catch (Exception e) {
}
}
private static void cglibBeanCopyTest(User source, User target, int sum) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < sum; i++) {
cglibBeanCopy(source,target);
}
stopWatch.stop();
System.out.println("使用org.springframework.cglib.beans.BeanCopier方式赋值"+sum+"个user对象,耗时:"+stopWatch.getLastTaskTimeMillis()+"毫秒");
}
/**
* org.springframework.cglib.beans.BeanCopier方式
*/
static BeanCopier copier = BeanCopier.create(User.class, User.class, false);
private static void cglibBeanCopy(User source, User target) {
copier.copy(source, target, null);
}
private static void springBeanCopyTest(User source, User target, int sum) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < sum; i++) {
springBeanCopy(source,target);
}
stopWatch.stop();
System.out.println("使用org.springframework.beans.BeanUtils.copyProperties方式赋值"+sum+"个user对象,耗时:"+stopWatch.getLastTaskTimeMillis()+"毫秒");
}
/**
* org.springframework.beans.BeanUtils.copyProperties方式
*/
private static void springBeanCopy(User source, User target) {
org.springframework.beans.BeanUtils.copyProperties(source, target);
}
}
"Four King Kong" performance statistics
method | 1000 | 10000 | 100000 | 1000000 |
---|---|---|---|---|
apache BeanUtils | 906 milliseconds | 807 milliseconds | 1892 milliseconds | 11049 milliseconds |
apache PropertyUtils | 17 milliseconds | 96 milliseconds | 648 milliseconds | 5896 milliseconds |
spring cglib BeanCopier | 0 milliseconds | 1 millisecond | 3 milliseconds | 10 milliseconds |
spring copyProperties | 87 milliseconds | 90 milliseconds | 123 milliseconds | 482 milliseconds |
Unexpectedly, I don’t know. I was shocked when I tested it. The difference is really much worse.
spring cglib BeanCopier
Best apache BeanUtils
performance, worst performance.
Performance trend --> spring cglib BeanCopier
better spring copyProperties
than better apache PropertyUtils
thanapache BeanUtils
The problem of avoiding the copying of attributes with Apache Beanutils has been analyzed above. Let's take a look at the optimizations made by other methods.
Apache PropertyUtils source code analysis
It can be clearly seen from the source code that the type check has become a non-null check, and the log record of each copy has been removed, and the performance must be better.
- Type checking becomes non-null checking
- Removed log records for each copy
- The actual assignment is changed from copyProperty to DanyBean + setSimpleProperty;
DanyBean provides the ability to dynamically modify the attribute name, attribute value, and attribute type of the class that implements it.
/**
* org.apache.commons.beanutils.PropertyUtils方式源码解析
* @author 哪吒编程
* @time 2023-01-07
*/
public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
// 判断数据源和目标对象不是null
if (dest == null) {
throw new IllegalArgumentException("No destination bean specified");
} else if (orig == null) {
throw new IllegalArgumentException("No origin bean specified");
} else {
// 删除了org.apache.commons.beanutils.BeanUtils.copyProperties中最为耗时的log日志记录
int var5;
int var6;
String name;
Object value;
// 类型检查
if (orig instanceof DynaBean) {
// 获取源对象所有属性
DynaProperty[] origDescriptors = ((DynaBean)orig).getDynaClass().getDynaProperties();
DynaProperty[] var4 = origDescriptors;
var5 = origDescriptors.length;
for(var6 = 0; var6 < var5; ++var6) {
DynaProperty origDescriptor = var4[var6];
// 获取源对象属性名
name = origDescriptor.getName();
// 判断源对象是否可读、判断目标对象是否可写
if (this.isReadable(orig, name) && this.isWriteable(dest, name)) {
try {
// 获取对应的值
value = ((DynaBean)orig).get(name);
// 相对于org.apache.commons.beanutils.BeanUtils.copyProperties此处有优化
// DanyBean 提供了可以动态修改实现他的类的属性名称、属性值、属性类型的功能
if (dest instanceof DynaBean) {
((DynaBean)dest).set(name, value);
} else {
// 每个属性都调用一次copyProperty
this.setSimpleProperty(dest, name, value);
}
} catch (NoSuchMethodException var12) {
if (this.log.isDebugEnabled()) {
this.log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", var12);
}
}
}
}
} else if (orig instanceof Map) {
Iterator entries = ((Map)orig).entrySet().iterator();
while(true) {
Map.Entry entry;
String name;
do {
if (!entries.hasNext()) {
return;
}
entry = (Map.Entry)entries.next();
name = (String)entry.getKey();
} while(!this.isWriteable(dest, name));
try {
if (dest instanceof DynaBean) {
((DynaBean)dest).set(name, entry.getValue());
} else {
this.setSimpleProperty(dest, name, entry.getValue());
}
} catch (NoSuchMethodException var11) {
if (this.log.isDebugEnabled()) {
this.log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", var11);
}
}
}
} else {
PropertyDescriptor[] origDescriptors = this.getPropertyDescriptors(orig);
PropertyDescriptor[] var16 = origDescriptors;
var5 = origDescriptors.length;
for(var6 = 0; var6 < var5; ++var6) {
PropertyDescriptor origDescriptor = var16[var6];
name = origDescriptor.getName();
if (this.isReadable(orig, name) && this.isWriteable(dest, name)) {
try {
value = this.getSimpleProperty(orig, name);
if (dest instanceof DynaBean) {
((DynaBean)dest).set(name, value);
} else {
this.setSimpleProperty(dest, name, value);
}
} catch (NoSuchMethodException var10) {
if (this.log.isDebugEnabled()) {
this.log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", var10);
}
}
}
}
}
}
}
Detect code performance through jvisualvm.exe
Then check the running status through jvisualvm.exe, and sure enough, logging.log4j
there is no more, and the others are basically unchanged.
Spring copyProperties source code analysis
- The non-empty judgment of judging the data source and target object is changed to an assertion;
- There is no log record for each copy;
- without
if (orig instanceof DynaBean) {
this type checking; - Added steps to release permissions;
/**
* org.springframework.beans.BeanUtils.copyProperties方法源码解析
* @author 哪吒编程
* @time 2023-01-07
*/
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
@Nullable String... ignoreProperties) throws BeansException {
// 判断数据源和目标对象不是null
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
/**
* 若target设置了泛型,则默认使用泛型
* 若是 editable 是 null,则此处忽略
* 一般情况下editable都默认为null
*/
Class<?> actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
// 获取target中全部的属性描述
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
// 需要忽略的属性
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
// 目标对象存在写入方法、属性不被忽略
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
/**
* 源对象存在读取方法、数据是可复制的
* writeMethod.getParameterTypes()[0]:获取 writeMethod 的第一个入参类型
* readMethod.getReturnType():获取 readMethod 的返回值类型
* 判断返回值类型和入参类型是否存在继承关系,只有是继承关系或相等的情况下,才会进行注入
*/
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
// 放开读取方法的权限
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
// 通过反射获取值
Object value = readMethod.invoke(source);
// 放开写入方法的权限
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
// 通过反射写入值
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
Summarize
Ali's friendly reminder, it is still very reasonable to avoid using Apache Beanutils
the object .copy
Apache Beanutils
The performance problem occurs in the type verification and logging of each copy;
Apache PropertyUtils has been optimized as follows:
- Type checking becomes non-null checking
- Removed log records for each copy
- The actual assignment is changed from copyProperty to DanyBean + setSimpleProperty;
Spring copyProperties has been optimized as follows:
- The non-empty judgment of judging the data source and target object is changed to an assertion;
- There is no log record for each copy;
- without
if (orig instanceof DynaBean) {
this type checking; - Added steps to release permissions;