踩坑BeanUtils.copy**()导致的业务处理速度过慢

背景

​ 做一个从mongoDB中实时查询并转化为月份进行统计功能,本地测试速度正常,没有什么影响,部署到线上,自己测试了下功能发现整体流程下来花费8秒时间.

定位问题

​ 起初以为是mongoDB查询的问题,有过这种类似的经历,准备着手去解决mongoDB慢查询问题,但是后面使用本地环境连接线上mongoDB定位问题的时候,发现查询时间很短只有100ms,可以接受.排除了这个问题

测试代码:

long fir = System.currentTimeMillis();
List<DvsCarOnline> onlineList = 		                     dvsCarOnlineService.listCarByMonthAndOrgName(startDate,endDate,orgName,upAreaCodes);
long sec = System.currentTimeMillis();
// ......
long three = System.currentTimeMillis();
log.error("总组装耗时"+(three-fir));
log.error("第一段查询"+(sec-fir));
使用 System.currentTimeMillis();来确认消耗的时间

耗时详情

​ 而在整体的时间中,发现组装数据的时长最长,尤其集中在第一段查询中,占据了99%的时间,但是是很不应该的事情,组装数据是很快的不会出现这种问题,除非是for循环嵌套,导致复杂度上升,而其中有这样一个代码片段

for(DvsCarOnline dvs:onlineList){
            DvsCarInstallVo dvsCarInstallVo=new DvsCarInstallVo();
            Beans.copyPropertiesNotNull(dvsCarInstallVo,dvs);
//		......
}

使用了项目中封装的工具类

Beans.copyPropertiesNotNull();

查看源码后,这下能确认,是因为这个工具类方法引出的问题.

解决方法

这里当时偷了个懒,使用Beans去赋值属性,结果出现了大问题,改成给Vo赋值的格式解决问题

for(DvsCarOnline dvs:onlineList){
            DvsCarInstallVo dvsCarInstallVo=new DvsCarInstallVo();
            /*
            * 弃用Beans.copy 浪费时间太
            * Beans.copyPropertiesNotNull(dvsCarInstallVo,dvs);*/
            dvsCarInstallVo.setCreateDate(dvs.getCreateDate());
            dvsCarInstallVo.setFleetName(dvs.getFleetName());
            dvsCarInstallVo.setLastStatus(dvs.getLastStatus());
            dvsCarInstallVo.setOrgName(dvs.getOrgName());
            dvsCarInstallVo.setPlateNo(dvs.getPlateNo());
            dvsCarInstallVoList.add(dvsCarInstallVo);
   //.......
   }
   

最后时间稳定为

修改后情况

复盘

为什么会出现这种问题呢,就要从Beans里去找出问题了

工具类的方法

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.ConvertUtilsBean;
import org.apache.commons.beanutils.PropertyUtilsBean;  

    /**
     * 复制source中不能为空的对象
     */
    public static void copyPropertiesNotNull(Object target, Object source) {
        try {
            NullAwareBeanUtilsBean.getInstance().copyProperties(target, source);
        } catch (Exception e) {
            throw Exceptions.unchecked(e);
        }
    }

使用的是apache的BeanUtils工具

再点入方法copyProperties(target,source);

    


public void copyProperties(final Object dest, final Object orig)
        throws IllegalAccessException, InvocationTargetException {

        // Validate existence of the specified beans
        if (dest == null) {
            throw new IllegalArgumentException
                    ("No destination bean specified");
        }
        if (orig == null) {
            throw new IllegalArgumentException("No origin bean specified");
        }
        if (log.isDebugEnabled()) {
            log.debug("BeanUtils.copyProperties(" + dest + ", " +
                      orig + ")");
        }

        // Copy the properties, converting as necessary
        if (orig instanceof DynaBean) {
            final DynaProperty[] origDescriptors =
                ((DynaBean) orig).getDynaClass().getDynaProperties();
            for (DynaProperty origDescriptor : origDescriptors) {
                final String name = origDescriptor.getName();
                // Need to check isReadable() for WrapDynaBean
                // (see Jira issue# BEANUTILS-61)
                if (getPropertyUtils().isReadable(orig, name) &&
                    getPropertyUtils().isWriteable(dest, name)) {
                    final Object value = ((DynaBean) orig).get(name);
                    copyProperty(dest, name, value);
                }
            }
        } else if (orig instanceof Map) {
            @SuppressWarnings("unchecked")
            final
            // Map properties are always of type <String, Object>
            Map<String, Object> propMap = (Map<String, Object>) orig;
            for (final Map.Entry<String, Object> entry : propMap.entrySet()) {
                final String name = entry.getKey();
                if (getPropertyUtils().isWriteable(dest, name)) {
                    copyProperty(dest, name, entry.getValue());
                }
            }
        } else /* if (orig is a standard JavaBean) */ {
            final PropertyDescriptor[] origDescriptors =
                getPropertyUtils().getPropertyDescriptors(orig);
            for (PropertyDescriptor origDescriptor : origDescriptors) {
                final String name = origDescriptor.getName();
                if ("class".equals(name)) {
                    continue; // No point in trying to set an object's class
                }
                if (getPropertyUtils().isReadable(orig, name) &&
                    getPropertyUtils().isWriteable(dest, name)) {
                    try {
                        final Object value =
                            getPropertyUtils().getSimpleProperty(orig, name);
                        copyProperty(dest, name, value);
                    } catch (final NoSuchMethodException e) {
                        // Should not happen
                    }
                }
            }
        }

    }

不提代码长度和功能,进入代码上看,可以看到 方法复制值的时候,使用了for循环去执行复制的过程,而我复制的值恰好横向字段特别多,这样就浪费了很长的时间,同时又存在在for循环中,List的size()越大,速度越慢.导致最终结果还不如一个set()方法实在.

总结

这个东西使用的时候需要注意,我自己是看公司项目很多地方有在使用,自己也就在不清楚用途的情况下去使用,这点很糟糕, 也就是只看目标不思考过程和前提.这块谨记教训吧,后续要仔细去确认情况再去使用.后面就保证不会再出现这个问题

后来者也谨记教训了~~~~

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值