실제 프로젝트 개발에서는 객체간 할당이 흔한데, 더블일레븐, 세킬 등 전자상거래 프로세스가 복잡해지면서 데이터 양도 늘어나고 효율성 문제도 대두됐다.
Q: 객체 간의 할당을 위한 코드를 작성한다면 어떻게 하시겠습니까?
대답: 그것에 대해 생각하지 말고 코드를 직접 시작하고 가져오고 설정하십시오.
Q: 아래 그림처럼?
A: 네, 어떻게 제 코드를 온라인에 올릴 수 있나요?
Q: 아니오, 단지 예를 들었습니다.
A: 이것은 상업적 비밀과 관련이 있으며 매우 심각한 문제입니다.
Q: 당신이 말을 아주 잘한다는 것을 알았습니다. 질문에 직접 대답할 수 있습니까?
답변: 좋아요 좋아요 저도 이런 글이 너무 낮다는 생각이 듭니다 저번에 이렇게 글을 쓰고 나서 거의 맞을 뻔 했습니다
- 개체가 너무 많습니다. ctrl c + strl v, 키보드가 거의 고장났습니다.
- 그리고 실수하기 쉽습니다. 주의를 기울이지 않으면 속성이 일치하지 않고 잘못된 값이 할당됩니다.
- 코드는 어리석어 보이고, 클래스는 수천 줄로 구성되어 있고, 모두 복사본을 가져오고 설정하며, 심지어 매우 우아한 이름 전송을 제공했습니다.
- 이름에서 속성 이름이 보이지 않는 경우 각 속성의 의미에 대한 주석을 추가해야 합니다(기본적으로 이런 할당 작업을 추가해야 합니다. 매우 중요);
- 코드는 유지 관리하기 번거롭습니다.
- 개체가 너무 많으면 클래스 폭발 문제가 발생하고 속성이 너무 많으면 Alibaba 코드 사양을 심각하게 위반합니다(메소드의 실제 코드는 최대 20줄).
질문: 알겠습니다. 해결 방법을 알려주세요.
답변: 매우 간단합니다. 도구 클래스 Beanutils를 통해 직접 값을 할당할 수 있습니다.
질문: 최근 도구가 매우 인기가 있다고 들었습니다. 어떤 도구를 사용하십니까?
대답: Apache
당신과 함께 제공되는 것만으로도 정말 간단합니다. 감사의 마음을 담아 손글씨로 적어보았습니다.
질문: 코드에서 오류를 보고합니다. Apache Beanutils를 사용하여 속성을 복사하지 마십시오.
답변: 오류가 없습니다. 심각한 경고일 뿐입니다.代码能跑就行,有问题再优化呗
Q. 태도는? 인사에 휘말리는 사람에게 엄중한 경고가 나오는 이유는?
답: 돈을 얼마나 벌고, 일을 얼마나 하느냐, 나는 XXX가 아니다. 성능 문제일 것이다.
Q. 구체적인 이유는 무엇인가요?
답변: 3000위안 apache copyProperties
이면 야 합니까?
싱글톤 모드를 통해 호출 copyProperties
되지만 각 메서드는 BeanUtilsBean.getInstance()
인스턴스에 해당하고 각 클래스 인스턴스는 인스턴스에 해당하므로 실제 싱글톤 모드가 아닙니다.
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
성능 병목 현상 --> 너무 많은 로그도 문제입니다.
소스 코드에서 알 수 있듯이 각각 copyProperties
여러 유형 검사를 수행하고 로그를 인쇄해야 합니다.
/**
* 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) {
}
}
}
}
}
}
jvisualvm.exe를 통한 코드 성능 감지
그런 다음 jvisualvm.exe
작동 상태를 확인하고 확실히 logging.log4j
시간 소모성에서 1위로 인상적으로 나열됩니다.
Q. 다른 좋은 방법은 없나요? 더 나은 성능
답변: 물론 있습니다. 제가 아는 한 4가지 도구가 있습니다. 사실 더 있을 수도 있습니다. 먼저 간단하게 소개하겠습니다.
- org.apache.commons.beanutils.BeanUtils;
- org.apache.commons.beanutils.PropertyUtils;
- org.springframework.cglib.beans.BeanCopier;
- org.springframework.beans.BeanUtils;
Q: 그럼 왜 사용하지 않습니까?
답: 알겠습니다. 시연해 보겠습니다.
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);
}
}
"사왕콩" 실적 통계
방법 | 1000 | 10000 | 100000 | 1000000 |
---|---|---|---|---|
아파치 BeanUtils | 906밀리초 | 807밀리초 | 1892밀리초 | 11049밀리초 |
아파치 PropertyUtils | 17밀리초 | 96밀리초 | 648밀리초 | 5896밀리초 |
스프링 cglib BeanCopier | 0밀리초 | 1밀리초 | 3밀리초 | 10밀리초 |
스프링 카피 속성 | 87밀리초 | 90밀리초 | 123밀리초 | 482밀리초 |
의외로 잘 모르겠습니다. 테스트를 했을 때 충격을 받았습니다. 그 차이는 정말 훨씬 더 심합니다.
spring cglib BeanCopier
최고의 apache BeanUtils
성능, 최악의 성능.
실적 추세 --> spring cglib BeanCopier
우수 spring copyProperties
대비 apache PropertyUtils
우수apache BeanUtils
Apache Beanutils로 속성 복사를 피하는 문제는 위에서 분석한 바 있으며, 다른 방법에 의한 최적화를 살펴보겠습니다.
Apache PropertyUtils 소스 코드 분석
유형 검사가 null이 아닌 검사가 되었고 각 복사본의 로그 레코드가 제거되었으며 성능이 더 좋아졌음을 소스 코드에서 명확하게 볼 수 있습니다.
- 유형 검사가 널이 아닌 검사로 변경됨
- 각 사본에 대한 로그 기록 제거
- 실제 할당은 copyProperty에서 DanyBean + setSimpleProperty로 변경됩니다.
DanyBean은 이를 구현하는 클래스의 속성 이름, 속성 값 및 속성 유형을 동적으로 수정하는 기능을 제공합니다.
/**
* 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);
}
}
}
}
}
}
}
jvisualvm.exe를 통한 코드 성능 감지
그런 다음 jvisualvm.exe를 통해 실행 상태를 확인하고 확실히 logging.log4j
더 이상 없으며 나머지는 기본적으로 변경되지 않습니다.
Spring copyProperties 소스 코드 분석
- 데이터 소스와 대상 개체를 판단하는 비어 있지 않은 판단이 주장으로 변경됩니다.
- 각 사본에 대한 로그 레코드가 없습니다.
if (orig instanceof DynaBean) {
이 유형 검사 없이 ;- 권한을 해제하는 단계를 추가했습니다.
/**
* 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);
}
}
}
}
}
}
요약하다
Ali의 친근한 알림, Apache Beanutils
객체 사용 copy
을 피하는 것은 여전히 매우 합리적 입니다.
Apache Beanutils
각 복사본의 형식 확인 및 로깅에서 성능 문제가 발생합니다.
Apache PropertyUtils는 다음과 같이 최적화되었습니다.
- 유형 검사가 널이 아닌 검사로 변경됨
- 각 사본에 대한 로그 기록 제거
- 실제 할당은 copyProperty에서 DanyBean + setSimpleProperty로 변경됩니다.
Spring copyProperties는 다음과 같이 최적화되었습니다.
- 데이터 소스와 대상 개체를 판단하는 비어 있지 않은 판단이 주장으로 변경됩니다.
- 각 사본에 대한 로그 레코드가 없습니다.
if (orig instanceof DynaBean) {
이 유형 검사 없이 ;- 권한을 해제하는 단계를 추가했습니다.