Apache Beanutils でプロパティをコピーしないでください。どうして?一緒に調べましょう。

実際のプロジェクト開発では、オブジェクト間の割り当てが一般的ですが、Double Eleven や Seckill などの電子商取引プロセスが複雑になるにつれて、データ量も増加し、効率の問題が表面化しています。

Q: オブジェクト間の代入のコードを書くとしたら、何をしますか?

回答: 考えずに、コードを直接開始し、取得して設定するだけです。

Q: 下の写真のように?

A: ええ、どうやって私のコードをオンラインに公開できますか?

Q: いいえ、例を挙げているだけです

A: これには企業秘密が含まれており、非常に深刻な問題です。

Q: あなたはとてもおしゃべりが上手だと思いましたが、質問に直接答えていただけますか?

回答: OK、OK、私もこのような書き込みは非常に低いと感じています. 前回このような書き込みをした後、私はほとんど殴られました.

  1. オブジェクトが多すぎます。ctrl c + strl v、キーボードがほとんど壊れています。
  2. 注意を怠ると、属性が対応せず、間違った値が割り当てられます。
  3. コードはばかげているように見えます。クラスには何千もの行があり、すべて get と set のコピーがあり、非常に洗練された名前の転送さえあります。
  4. 名前から属性名が見えない場合は、各属性の意味についてコメントを追加する必要があります (基本的に、このような代入操作を追加する必要があります。コメントは非常に重要です。コメントは非常に重要です。コメントは非常に重要です。非常に重要);
  5. コードの保守は面倒です。
  6. オブジェクトが多すぎると、クラス爆発の問題が発生し、属性が多すぎると、アリババのコード仕様 (メソッドの実際のコードは最大で 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 種類あります.実際にはもっと多くの種類があるかもしれません..

  1. org.apache.commons.beanutils.BeanUtils;
  2. org.apache.commons.beanutils.PropertyUtils;
  3. org.springframework.cglib.beans.BeanCopier;
  4. 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 チェックになり、各コピーのログ レコードが削除され、パフォーマンスが向上していることがソース コードから明確にわかります。

  1. 型チェックが非 null チェックになる
  2. 各コピーの削除されたログ レコード
  3. 実際の割り当ては、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 ソースコード分析

  1. データ ソースとターゲット オブジェクトを判断する空でない判断は、アサーションに変更されます。
  2. 各コピーのログ レコードはありません。
  3. if (orig instanceof DynaBean) { この型チェックなし。
  4. 権限を解放する手順を追加しました。
/**
 * 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 は次のように最適化されています。

  1. 型チェックが非 null チェックになる
  2. 各コピーの削除されたログ レコード
  3. 実際の割り当ては、copyProperty から DanyBean + setSimpleProperty に変更されます。

Spring copyProperties は次のように最適化されています。

  1. データ ソースとターゲット オブジェクトを判断する空でない判断は、アサーションに変更されます。
  2. 各コピーのログ レコードはありません。
  3. if (orig instanceof DynaBean) { この型チェックなし。
  4. 権限を解放する手順を追加しました。

おすすめ

転載: blog.csdn.net/guorui_java/article/details/128561652