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
- Há muitos objetos, ctrl c + strl v, o teclado está quase quebrado;
- E é fácil cometer erros, se você não prestar atenção, o atributo não corresponde e o valor errado é atribuído;
- 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;
- 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);
- O código é complicado de manter;
- 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ó Apache
o 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 copyProperties
o 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 copyProperties
precisa 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.exe
verifique o status da operação e, com certeza, está logging.log4j
listado 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.
- org.apache.commons.beanutils.BeanUtils;
- org.apache.commons.beanutils.PropertyUtils;
- org.springframework.cglib.beans.BeanCopier;
- 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 BeanCopier
Melhor apache BeanUtils
desempenho, pior desempenho.
Tendência de desempenho --> spring cglib BeanCopier
melhor spring copyProperties
que melhor apache PropertyUtils
queapache 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.
- Verificação de tipo torna-se verificação não nula
- Registros de log removidos para cada cópia
- 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.log4j
não há mais, e os outros estão basicamente inalterados.
Spring copyProperties análise de código-fonte
- O julgamento não vazio de julgar a fonte de dados e o objeto de destino é alterado para uma asserção;
- Não há registro de log para cada cópia;
- sem
if (orig instanceof DynaBean) {
esta verificação de tipo; - 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 Beanutils
do objeto .copy
Apache Beanutils
O problema de performance ocorre na verificação do tipo e registro de cada cópia;
O Apache PropertyUtils foi otimizado da seguinte forma:
- Verificação de tipo torna-se verificação não nula
- Registros de log removidos para cada cópia
- A atribuição real é alterada de copyProperty para DanyBean + setSimpleProperty;
Spring copyProperties foi otimizado da seguinte forma:
- O julgamento não vazio de julgar a fonte de dados e o objeto de destino é alterado para uma asserção;
- Não há registro de log para cada cópia;
- sem
if (orig instanceof DynaBean) {
esta verificação de tipo; - Adicionadas etapas para liberação de permissões;