Evite copiar propriedades com Apache Beanutils. porque? Vamos descobrir juntos.

No desenvolvimento de projetos reais, a atribuição entre objetos é comum.À medida que os processos de comércio eletrônico, como Double Eleven e Seckil, se tornam mais complexos, a quantidade de dados também aumenta e surgem problemas de eficiência.

P: Se você fosse escrever o código para atribuição entre objetos, o que faria?

Resposta: Nem pense nisso, apenas inicie o código diretamente, apenas pegue e defina.

P: Gostou da foto abaixo?

A: Sim, como você pode colocar meu código online?

P: Não, estou apenas dando um exemplo

R: Isso envolve segredos comerciais e é um assunto muito sério

P: Eu descobri que você é muito bom em falar, você pode responder a pergunta diretamente?

Resposta: OK, OK, também sinto que escrever assim é muito baixo. Depois de escrever assim da última vez, quase fui espancado

  1. Há muitos objetos, ctrl c + strl v, o teclado está quase quebrado;
  2. E é fácil cometer erros, se você não prestar atenção, o atributo não corresponde e o valor errado é atribuído;
  3. O código parece estúpido, uma classe tem milhares de linhas, todas as cópias get e set, e até deu uma transferência de nome muito elegante;
  4. Se o nome do atributo não pode ser visto a partir do nome, você deve adicionar um comentário sobre o significado de cada atributo (basicamente, esse tipo de operação de atribuição deve ser adicionado, o comentário é muito importante, o comentário é muito importante, o comentário é muito importante);
  5. O código é complicado de manter;
  6. Se houver muitos objetos, haverá um problema de explosão de classe.Se houver muitos atributos, isso violará seriamente a especificação do código Alibaba (o código real de um método é de no máximo 20 linhas);

Pergunta: Ok, ok, diga-me como resolvê-lo.

Resposta: É bem simples, você pode atribuir valores diretamente através da classe de ferramenta Beanutils

Pergunta: Ouvi dizer que as ferramentas são muito populares recentemente, qual você usa?

Resposta: Só Apacheo que vem com você, é bem simples. Escrevi um à mão para sua apreciação.

Pergunta: Seu código reporta um erro, evite usar Apache Beanutils para copiar atributos.

Resposta: Nenhum erro, apenas um aviso sério,代码能跑就行,有问题再优化呗

P: Qual é a sua atitude? Por que há um aviso sério para a pessoa que se envolve em assuntos pessoais?

Resposta: Quanto dinheiro levar, quanto trabalho fazer, não sou XXX, deve ser um problema de desempenho

P: Qual é o motivo específico?

Resposta: Por 3.000 yuan, você tem que rasgar apache copyPropertieso código-fonte manualmente?

Chamado por meio do modo singleton copyProperties, no entanto, cada método corresponde a uma BeanUtilsBean.getInstance()instância e cada instância de classe corresponde a uma instância, o que não é um modo singleton real.

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

Gargalo de desempenho --> Muitos logs também são um problema

Como você pode ver no código-fonte, cada um deles copyPropertiesprecisa executar várias verificações de tipo e imprimir 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) {
    
    
                    }
                }
            }
        }

    }
}

Detecte o desempenho do código por meio do jvisualvm.exe

Em seguida, jvisualvm.exeverifique o status da operação e, com certeza, está logging.log4jlistado de forma impressionante, ocupando o primeiro lugar em consumo de tempo.

Q: Existe alguma outra boa maneira? melhor performance

Resposta: Claro que existem. Tanto quanto eu sei, existem 4 tipos de ferramentas. Na verdade, pode haver mais. Sem mais delongas, deixe-me apresentá-los brevemente primeiro.

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

P: Então por que você não usa?

Resposta: OK, deixe-me demonstrar

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

Estatísticas de desempenho de "Quatro King Kong"

método 1000 10000 100000 1000000
apache BeanUtils 906 milissegundos 807 milissegundos 1892 milissegundos 11049 milissegundos
Apache PropertyUtils 17 milissegundos 96 milissegundos 648 milissegundos 5896 milissegundos
spring cglib BeanCopier 0 milissegundos 1 milissegundo 3 milissegundos 10 milissegundos
spring copyPropriedades 87 milissegundos 90 milissegundos 123 milissegundos 482 milissegundos

Inesperadamente, não sei. Fiquei chocado quando testei. A diferença é realmente muito pior.

spring cglib BeanCopierMelhor apache BeanUtilsdesempenho, pior desempenho.

Tendência de desempenho --> spring cglib BeanCopiermelhor spring copyPropertiesque melhor apache PropertyUtilsqueapache BeanUtils

O problema de evitar a cópia de atributos com o Apache Beanutils foi analisado acima, vamos dar uma olhada nas otimizações feitas por outros métodos.

Análise de código-fonte do Apache PropertyUtils

Pode ser visto claramente no código-fonte que a verificação de tipo se tornou uma verificação não nula e o registro de log de cada cópia foi removido e o desempenho deve ser melhor.

  1. Verificação de tipo torna-se verificação não nula
  2. Registros de log removidos para cada cópia
  3. A atribuição real é alterada de copyProperty para DanyBean + setSimpleProperty;

O DanyBean fornece a capacidade de modificar dinamicamente o nome do atributo, o valor do atributo e o tipo de atributo da classe que o 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);
                        }
                    }
                }
            }
        }

    }
}

Detecte o desempenho do código por meio do jvisualvm.exe

Em seguida, verifique o status de execução por meio de jvisualvm.exe e, com certeza, logging.log4jnão há mais, e os outros estão basicamente inalterados.

Spring copyProperties análise de código-fonte

  1. O julgamento não vazio de julgar a fonte de dados e o objeto de destino é alterado para uma asserção;
  2. Não há registro de log para cada cópia;
  3. sem if (orig instanceof DynaBean) { esta verificação de tipo;
  4. Adicionadas etapas para liberação de permissões;
/**
 * 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

Lembrete amigável de Ali, ainda é muito razoável evitar o uso Apache Beanutilsdo objeto .copy

Apache BeanutilsO problema de performance ocorre na verificação do tipo e registro de cada cópia;

O Apache PropertyUtils foi otimizado da seguinte forma:

  1. Verificação de tipo torna-se verificação não nula
  2. Registros de log removidos para cada cópia
  3. A atribuição real é alterada de copyProperty para DanyBean + setSimpleProperty;

Spring copyProperties foi otimizado da seguinte forma:

  1. O julgamento não vazio de julgar a fonte de dados e o objeto de destino é alterado para uma asserção;
  2. Não há registro de log para cada cópia;
  3. sem if (orig instanceof DynaBean) { esta verificação de tipo;
  4. Adicionadas etapas para liberação de permissões;

Acho que você gosta

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