Avoid copying properties with Apache Beanutils. why? Let's find out together.

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

  1. There are too many objects, ctrl c + strl v, the keyboard is almost broken;
  2. And it is easy to make mistakes, if you are not paying attention, the attribute does not correspond, and the wrong value is assigned;
  3. The code looks stupid, a class has thousands of lines, all get and set copies, and even gave it a very elegant name transfer;
  4. 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);
  5. The code is cumbersome to maintain;
  6. 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 Apachethe 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 copyPropertiesthe 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 copyPropertieshas 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.execheck the operation status, and sure enough, it is logging.log4jimpressively 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.

  1. org.apache.commons.beanutils.BeanUtils;
  2. org.apache.commons.beanutils.PropertyUtils;
  3. org.springframework.cglib.beans.BeanCopier;
  4. 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 BeanCopierBest apache BeanUtilsperformance, worst performance.

Performance trend --> spring cglib BeanCopierbetter spring copyPropertiesthan better apache PropertyUtilsthanapache 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.

  1. Type checking becomes non-null checking
  2. Removed log records for each copy
  3. 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.log4jthere is no more, and the others are basically unchanged.

Spring copyProperties source code analysis

  1. The non-empty judgment of judging the data source and target object is changed to an assertion;
  2. There is no log record for each copy;
  3. without if (orig instanceof DynaBean) { this type checking;
  4. 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 Beanutilsthe object .copy

Apache BeanutilsThe performance problem occurs in the type verification and logging of each copy;

Apache PropertyUtils has been optimized as follows:

  1. Type checking becomes non-null checking
  2. Removed log records for each copy
  3. The actual assignment is changed from copyProperty to DanyBean + setSimpleProperty;

Spring copyProperties has been optimized as follows:

  1. The non-empty judgment of judging the data source and target object is changed to an assertion;
  2. There is no log record for each copy;
  3. without if (orig instanceof DynaBean) { this type checking;
  4. Added steps to release permissions;

Guess you like

Origin blog.csdn.net/guorui_java/article/details/128561652