Evite copiar propiedades con Apache Beanutils. ¿por qué? Averigüémoslo juntos.

En el desarrollo de proyectos reales, la asignación entre objetos es común. A medida que los procesos de comercio electrónico como Double Eleven y Seckill se vuelven más complejos, la cantidad de datos también aumenta y surgen problemas de eficiencia.

P: Si tuviera que escribir el código para la asignación entre objetos, ¿qué haría?

Respuesta: Ni siquiera lo piense, solo inicie el código directamente, solo obtenga y configure.

P: ¿Te gusta la imagen de abajo?

R: Sí, ¿cómo puedes poner mi código en línea?

P: No, solo estoy dando un ejemplo.

R: Esto involucra secretos comerciales y es un tema muy serio.

P: Descubrí que eres muy bueno hablando, ¿puedes responder la pregunta directamente?

Respuesta: OK, OK, también siento que escribir así es muy bajo. Después de escribir así la última vez, casi me golpean.

  1. Hay demasiados objetos, ctrl c + strl v, el teclado está casi roto;
  2. Y es fácil cometer errores, si no estás prestando atención, el atributo no corresponde y se asigna un valor incorrecto;
  3. El código parece estúpido, una clase tiene miles de líneas, todas obtienen y establecen copias, e incluso le dieron una transferencia de nombre muy elegante;
  4. Si el nombre del atributo no se puede ver desde el nombre, debe agregar un comentario sobre el significado de cada atributo (básicamente, se debe agregar este tipo de operación de asignación, el comentario es muy importante, el comentario es muy importante, el comentario es muy importante);
  5. El código es engorroso de mantener;
  6. Si hay demasiados objetos, habrá un problema de explosión de clases.Si hay demasiados atributos, violará gravemente la especificación del código de Alibaba (el código real de un método tiene como máximo 20 líneas);

Pregunta: Está bien, está bien, dime cómo resolverlo.

Respuesta: Es muy simple, puedes asignar valores directamente a través de la clase de herramienta Beanutils

Pregunta: Escuché que las herramientas son muy populares recientemente, ¿cuál usas?

Respuesta: Solo Apacheel que viene contigo, es muy sencillo. Escribí uno a mano para su apreciación.

Pregunta: Su código informa un error, evite usar Apache Beanutils para copiar atributos.

Respuesta: No hay error, solo una advertencia seria,代码能跑就行,有问题再优化呗

P: ¿Cuál es tu actitud? ¿Por qué hay una advertencia seria para la persona que se dibuja en los asuntos de personal?

Respuesta: ¿Cuánto dinero tomar, cuánto trabajo hacer, no soy XXX, debería ser un problema de rendimiento?

P: ¿Cuál es la razón específica?

Respuesta: ¿Por 3000 yuanes, tienes que romper apache copyPropertiesel código fuente a mano?

Sin embargo, llamado a través del modo singleton copyProperties, cada método corresponde a una BeanUtilsBean.getInstance()instancia, y cada instancia de clase corresponde a una instancia, que no es un modo singleton real.

public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
    
    
	BeanUtilsBean.getInstance().copyProperties(dest, orig);
}

Cuello de botella en el rendimiento --> Demasiados registros también son un problema

Como puede ver en el código fuente, cada uno de ellos copyPropertiesdebe realizar varias comprobaciones de tipos e imprimir registros.

/**
 * 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) {
    
    
                    }
                }
            }
        }

    }
}

Detectar el rendimiento del código a través de jvisualvm.exe

Luego jvisualvm.exeverifique el estado de la operación y, por supuesto, está en logging.log4juna lista impresionante, clasificándose entre los 1 primeros en cuanto a consumo de tiempo.

P: ¿Hay alguna otra buena manera? mejor interpretación

Respuesta: Por supuesto que las hay. Hasta donde yo sé, hay 4 tipos de herramientas. De hecho, puede haber más. Sin más preámbulos, permítanme presentarlas brevemente primero.

  1. org.apache.commons.beanutils.BeanUtils;
  2. org.apache.commons.beanutils.PropertyUtils;
  3. org.springframework.cglib.beans.BeanCopier;
  4. org.springframework.beans.BeanUtils;

P: Entonces, ¿por qué no lo usas?

Respuesta: OK, déjame demostrarte

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);
    }
}

Estadísticas de rendimiento de "Cuatro King Kong"

método 1000 10000 100000 1000000
apache beanutils 906 milisegundos 807 milisegundos 1892 milisegundos 11049 milisegundos
apache PropertyUtils 17 milisegundos 96 milisegundos 648 milisegundos 5896 milisegundos
primavera cglib BeanCopier 0 milisegundos 1 milisegundo 3 milisegundos 10 milisegundos
Spring copyProperties 87 milisegundos 90 milisegundos 123 milisegundos 482 milisegundos

Inesperadamente, no lo sé. Me sorprendió cuando lo probé. La diferencia es realmente mucho peor.

spring cglib BeanCopierMejor apache BeanUtilsactuación, peor actuación.

Tendencia de rendimiento --> spring cglib BeanCopiermejor spring copyPropertiesque mejor apache PropertyUtilsqueapache BeanUtils

El problema de evitar la copia de atributos con Apache Beanutils se ha analizado anteriormente, echemos un vistazo a las optimizaciones realizadas por otros métodos.

Análisis de código fuente de Apache PropertyUtils

Se puede ver claramente en el código fuente que la verificación de tipo se ha convertido en una verificación no nula, y el registro de registro de cada copia se ha eliminado, y el rendimiento debe ser mejor.

  1. La verificación de tipos se convierte en una verificación no nula
  2. Registros de registro eliminados para cada copia
  3. La asignación real se cambia de copyProperty a DanyBean + setSimpleProperty;

DanyBean brinda la capacidad de modificar dinámicamente el nombre del atributo, el valor del atributo y el tipo de atributo de la clase que lo implementa.

/**
 * 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);
                        }
                    }
                }
            }
        }

    }
}

Detectar el rendimiento del código a través de jvisualvm.exe

Luego verifique el estado de ejecución a través de jvisualvm.exe y, por supuesto, logging.log4jno hay más, y los demás básicamente no han cambiado.

Análisis de código fuente de Spring copyProperties

  1. El juicio no vacío de juzgar la fuente de datos y el objeto de destino se cambia a una afirmación;
  2. No hay registro de registro para cada copia;
  3. sin if (orig instanceof DynaBean) { esta verificación de tipo;
  4. Se agregaron pasos para liberar permisos;
/**
 * 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);
                    }
                }
            }
        }
    }
}

Resumir

Recordatorio amistoso de Ali, todavía es muy razonable evitar usar Apache Beanutilsel objeto .copy

Apache BeanutilsEl problema de rendimiento ocurre en la verificación de tipo y el registro de cada copia;

Apache PropertyUtils se ha optimizado de la siguiente manera:

  1. La verificación de tipos se convierte en una verificación no nula
  2. Registros de registro eliminados para cada copia
  3. La asignación real se cambia de copyProperty a DanyBean + setSimpleProperty;

Spring copyProperties se ha optimizado de la siguiente manera:

  1. El juicio no vacío de juzgar la fuente de datos y el objeto de destino se cambia a una afirmación;
  2. No hay registro de registro para cada copia;
  3. sin if (orig instanceof DynaBean) { esta verificación de tipo;
  4. Se agregaron pasos para liberar permisos;

Supongo que te gusta

Origin blog.csdn.net/guorui_java/article/details/128561652
Recomendado
Clasificación