実際のプロジェクト開発では、オブジェクト間の割り当てが一般的ですが、Double Eleven や Seckill などの電子商取引プロセスが複雑になるにつれて、データ量も増加し、効率の問題が表面化しています。
Q: オブジェクト間の代入のコードを書くとしたら、何をしますか?
回答: 考えずに、コードを直接開始し、取得して設定するだけです。
Q: 下の写真のように?
A: ええ、どうやって私のコードをオンラインに公開できますか?
Q: いいえ、例を挙げているだけです
A: これには企業秘密が含まれており、非常に深刻な問題です。
Q: あなたはとてもおしゃべりが上手だと思いましたが、質問に直接答えていただけますか?
回答: OK、OK、私もこのような書き込みは非常に低いと感じています. 前回このような書き込みをした後、私はほとんど殴られました.
- オブジェクトが多すぎます。ctrl c + strl v、キーボードがほとんど壊れています。
- 注意を怠ると、属性が対応せず、間違った値が割り当てられます。
- コードはばかげているように見えます。クラスには何千もの行があり、すべて get と set のコピーがあり、非常に洗練された名前の転送さえあります。
- 名前から属性名が見えない場合は、各属性の意味についてコメントを追加する必要があります (基本的に、このような代入操作を追加する必要があります。コメントは非常に重要です。コメントは非常に重要です。コメントは非常に重要です。非常に重要);
- コードの保守は面倒です。
- オブジェクトが多すぎると、クラス爆発の問題が発生し、属性が多すぎると、アリババのコード仕様 (メソッドの実際のコードは最大で 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: じゃあ使わないの?
回答: OK、デモンストレーションしましょう
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 |
---|---|---|---|---|
Apache BeanUtils | 906 ミリ秒 | 807ミリ秒 | 1892 ミリ秒 | 11049 ミリ秒 |
Apache 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 チェックになり、各コピーのログ レコードが削除され、パフォーマンスが向上していることがソース コードから明確にわかります。
- 型チェックが非 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);
}
}
}
}
}
}
要約する
アリの親切なリマインダーですがApache Beanutils
、オブジェクトの使用copy
を避けることは依然として非常に合理的です。
Apache Beanutils
パフォーマンスの問題は、各コピーの型の検証とログ記録で発生します。
Apache PropertyUtils は次のように最適化されています。
- 型チェックが非 null チェックになる
- 各コピーの削除されたログ レコード
- 実際の割り当ては、copyProperty から DanyBean + setSimpleProperty に変更されます。
Spring copyProperties は次のように最適化されています。
- データ ソースとターゲット オブジェクトを判断する空でない判断は、アサーションに変更されます。
- 各コピーのログ レコードはありません。
if (orig instanceof DynaBean) {
この型チェックなし。- 権限を解放する手順を追加しました。