当前位置:首页 » 《随便一记》 » 正文

跟同事杠上了,Apache Beanutils为什么被禁止使用?

18 人参与  2023年03月07日 08:01  分类 : 《随便一记》  评论

点击全文阅读


在这里插入图片描述

收录于热门专栏Java基础教程系列(进阶篇)

在实际的项目开发中,对象间赋值普遍存在,随着双十一、秒杀等电商过程愈加复杂,数据量也在不断攀升,效率问题,浮出水面。

问:如果是你来写对象间赋值的代码,你会怎么做?

答:想都不用想,直接代码走起来,get、set即可。

问:下图这样?

答:对啊,你怎么能把我的代码放到网上?

问:没,我只是举个例子

答:这涉及到商业机密,是很严重的问题

问:我发现你挺能扯皮啊,直接回答问题行吗?

答:OK,OK,我也觉得这样写很low,上次这么写之后,差点挨打

对象太多,ctrl c + strl v,键盘差点没敲坏;而且很容易出错,一不留神,属性没对应上,赋错值了;代码看起来很傻缺,一个类好几千行,全是get、set复制,还起个了自以为很优雅的名字transfer;如果属性名不能见名知意,还得加上每个属性的含义注释(基本这种赋值操作,都是要加的,注释很重要,注释很重要,注释很重要);代码维护起来很麻烦;如果对象过多,会产生类爆炸问题,如果属性过多,会严重违背阿里巴巴代码规约(一个方法的实际代码最多20行);

问:行了,行了,说说,怎么解决吧。

答:很简单啊,可以通过工具类Beanutils直接赋值啊

问:我听说工具类最近很卷,你用的哪个啊?

答:就Apache自带的那个啊,贼简单。我手写一个,给你欣赏一下。

问:你这代码报错啊,避免用Apache Beanutils进行属性的copy。

答:没报错,只是严重警告而已,代码能跑就行,有问题再优化呗

问:你这什么态度?人事在哪划拉的人,为啥会出现严重警告?

答:拿多少钱,干多少活,我又不是XXX,应该是性能问题吧

问:具体什么原因导致的呢?

答: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赫然在列,稳居耗时Top1。

问:还有其它好的方式吗?性能好一点的

答:当然有,据我了解有 4 种工具类,实际上,可能会有更多,话不多说,先简单介绍一下。

org.apache.commons.beanutils.BeanUtils;org.apache.commons.beanutils.PropertyUtils;org.springframework.cglib.beans.BeanCopier;org.springframework.beans.BeanUtils;

问:那你怎么不用?

答: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("666666666@qq.com");        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);    }}

“四大金刚” 性能统计

方法1000100001000001000000
apache BeanUtils906毫秒807毫秒1892毫秒11049毫秒
apache PropertyUtils17毫秒96毫秒648毫秒5896毫秒
spring cglib BeanCopier0毫秒1毫秒3毫秒10毫秒
spring copyProperties87毫秒90毫秒123毫秒482毫秒

不测不知道,一测吓一跳,差的还真的多。

spring cglib BeanCopier性能最好,apache BeanUtils性能最差。

性能走势 --> spring cglib BeanCopier优于 spring copyProperties优于 apache PropertyUtils优于 apache BeanUtils

避免用Apache Beanutils进行属性的copy的问题 上面分析完了,下面再看看其它的方法做了哪些优化。

Apache PropertyUtils 源码分析

从源码可以清晰的看到,类型检查变成了非空校验,去掉了每一次copy的日志记录,性能肯定更好了。

类型检查变成了非空校验去掉了每一次copy的日志记录实际赋值的地方由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 源码分析

判断数据源和目标对象的非空判断改为了断言;每次copy没有日志记录;没有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的性能问题出现在类型校验和每一次copy的日志记录;

Apache PropertyUtils 进行了如下优化:

类型检查变成了非空校验去掉了每一次copy的日志记录实际赋值的地方由copyProperty变成了DanyBean + setSimpleProperty;

Spring copyProperties 进行了如下优化:

判断数据源和目标对象的非空判断改为了断言;每次copy没有日志记录;没有if (orig instanceof DynaBean) {这个类型检查;增加了放开权限的步骤;

在这里插入图片描述
在这里插入图片描述

Java学习路线总结,搬砖工逆袭Java架构师

10万字208道Java经典面试题总结(附答案)

Java基础教程系列

Java基础教程系列(进阶篇)


点击全文阅读


本文链接:http://zhangshiyu.com/post/54080.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1