码迷,mamicode.com
首页 > 其他好文 > 详细

结合阿里代码规范约定+源码剖析属性拷贝性能安全问题

时间:2020-05-20 17:14:30      阅读:47      评论:0      收藏:0      [点我收藏+]

标签:contain   ace   private   slist   lis   字符串拼接   framework   base   大量   

结合阿里代码规范约定+源码剖析属性拷贝安全,性能问题

org.springframework.beans.BeanUtils源码为例      

技术图片

第一个标记处校验了源对象中是否有目标对象中需要更新的某属性,如果有就往下执行了
第二个标记处从原对象中把该属性值取出,然后设置到目标对象中相应属性上

对比了其他几种BeanUtils源码基本都是这个实现思路,大同小异,并不会检查原对象的属性是否为null,我想这可能是因为BeanUtils拷贝属性的设计初衷就是null也是一种属性值的状态
【不是不可以使用,请大家使用前务必多做了解,测试;再投入使用】

 

**********************************************************************************************************************************************************************************************

测试各copy的性能

 

自定义接口测试方法

public interface PropertiesCopier {
    void copyProperties(Object source, Object target) throws Exception;
}
public class CglibBeanCopierPropertiesCopier implements PropertiesCopier {
    @Override
    public void copyProperties(Object source, Object target) throws Exception {
        BeanCopier copier = BeanCopier.create(source.getClass(), target.getClass(), false);
        copier.copy(source, target, null);
    }
}
// 全局静态 BeanCopier,避免每次都生成新的对象
public class StaticCglibBeanCopierPropertiesCopier implements PropertiesCopier {
    private static BeanCopier copier = BeanCopier.create(Account.class, Account.class, false);
    @Override
    public void copyProperties(Object source, Object target) throws Exception {
        copier.copy(source, target, null);
    }
}
public class SpringBeanUtilsPropertiesCopier implements PropertiesCopier {
    @Override
    public void copyProperties(Object source, Object target) throws Exception {
        org.springframework.beans.BeanUtils.copyProperties(source, target);
    }
}
public class CommonsBeanUtilsPropertiesCopier implements PropertiesCopier {
    @Override
    public void copyProperties(Object source, Object target) throws Exception {
        org.apache.commons.beanutils.BeanUtils.copyProperties(target, source);
    }
}
public class CommonsPropertyUtilsPropertiesCopier implements PropertiesCopier {
    @Override
    public void copyProperties(Object source, Object target) throws Exception {
        org.apache.commons.beanutils.PropertyUtils.copyProperties(target, source);
    }
}

参数化单元测试代码:

@RunWith(Parameterized.class)
public class PropertiesCopierTest {
    @Parameterized.Parameter(0)
    public PropertiesCopier propertiesCopier;
    // 测试次数
    private static List<Integer> testTimes = Arrays.asList(100, 1000, 10_000, 100_000, 1_000_000);
    // 测试结果以 markdown 表格的形式输出
    private static StringBuilder resultBuilder = new StringBuilder("|实现|100|1,000|10,000|100,000|1,000,000|\n").append("|----|----|----|----|----|----|\n");

    @Parameterized.Parameters
    public static Collection<Object[]> data() {
        Collection<Object[]> params = new ArrayList<>();
        params.add(new Object[]{new StaticCglibBeanCopierPropertiesCopier()});
        params.add(new Object[]{new CglibBeanCopierPropertiesCopier()});
        params.add(new Object[]{new SpringBeanUtilsPropertiesCopier()});
        params.add(new Object[]{new CommonsPropertyUtilsPropertiesCopier()});
        params.add(new Object[]{new CommonsBeanUtilsPropertiesCopier()});
        return params;
    }

    @Before
    public void setUp() throws Exception {
        String name = propertiesCopier.getClass().getSimpleName().replace("PropertiesCopier", "");
        resultBuilder.append("|").append(name).append("|");
    }

    @Test
    public void copyProperties() throws Exception {
        Account source = new Account(1, "test1", 30D);
        Account target = new Account();
        // 预热一次
        propertiesCopier.copyProperties(source, target);
        for (Integer time : testTimes) {
            long start = System.nanoTime();
            for (int i = 0; i < time; i++) {
                propertiesCopier.copyProperties(source, target);
            }
            resultBuilder.append((System.nanoTime() - start) / 1_000_000D).append("|");
        }
        resultBuilder.append("\n");
    }

    @AfterClass
    public static void tearDown() throws Exception {
        System.out.println("测试结果:");
        System.out.println(resultBuilder);
    }
}

 

测试结果汇总:

技术图片

结果表明,Cglib 的 BeanCopier 的拷贝速度是最快的,即使是百万次的拷贝也只需要 10 毫秒!
相比而言,最差的是 Commons 包的 BeanUtils.copyProperties 方法,100 次拷贝测试与表现最好的 Cglib 相差 400 倍之多。百万次拷贝更是出现了 2800 倍的性能差异!

是什么原因导致它存在这么大差异呢? 我们接着往下看源码


public void copyProperties(final Object dest, final Object orig)
        throws IllegalAccessException, InvocationTargetException {
        // 类型检查 
        if (orig instanceof DynaBean) {
            ...
        } else if (orig instanceof Map) {
           ...
        } else {
            final PropertyDescriptor[] origDescriptors = ...
            for (PropertyDescriptor origDescriptor : origDescriptors) {
                ...
                // 这里每个属性都调一次 copyProperty
                copyProperty(dest, name, value);
            }
        }
    }

    public void copyProperty(final Object bean, String name, Object value)
        throws IllegalAccessException, InvocationTargetException {
        ...
        // 这里又进行一次类型检查
        if (target instanceof DynaBean) {
            ...
        }
        ...
        // 需要将属性转换为目标类型
        value = convertForCopy(value, type);
        ...
    }
    // 而这个 convert 方法在日志级别为 debug 的时候有很多的字符串拼接
    public <T> T convert(final Class<T> type, Object value) {
        if (log().isDebugEnabled()) {
            log().debug("Converting" + (value == null ? "" : " ‘" + toString(sourceType) + "‘") + " value ‘" + value + "‘ to type ‘" + toString(targetType) + "‘");
        }
        ...
        if (targetType.equals(String.class)) {
            return targetType.cast(convertToString(value));
        } else if (targetType.equals(sourceType)) {
            if (log().isDebugEnabled()) {
                log().debug("No conversion required, value is already a " + toString(targetType));
            }
            return targetType.cast(value);
        } else {
            // 这个 convertToType 方法里也需要做类型检查
            final Object result = convertToType(targetType, value);
            if (log().isDebugEnabled()) {
                log().debug("Converted to " + toString(targetType) + " value ‘" + result + "‘");
            }
            return targetType.cast(result);
        }
    }

  

你会发现

common的工具类存在反复对象的比对,检查,以及类型转换;在加之又输出了大量的log  其实这就是直接导致其比较慢的原因 

总结两点:1   反复对象比对 + 转型     2  大量log的输出

 

***************************************************************************************************************************************************************************

其实就算使用cglib的工具类,频繁使用也会引起性能瓶颈问题

下面给大家推荐一套解决方案,1、选择cglib的工具类   2,即使频繁使用也不会导致性能瓶颈

/**
 * 
* @ClassName: CacheBeanCopier
* <b>Copyright xxxx
* @Description: 缓存 Bean Copier提升性能
* @author liuhanlin
* @date 2020年5月20日 下午4:01:34
*
 */
public class CacheBeanCopier {
    
    private static final Map<String, BeanCopier> BEAN_COPIERS = new HashMap<String, BeanCopier>();

    public static void copy(Object source, Object target){
        String key = genKey(source.getClass(), target.getClass());
        BeanCopier copier = null;
        if(!BEAN_COPIERS.containsKey(key)){
            copier = BeanCopier.create(source.getClass(), target.getClass(), false);
            BEAN_COPIERS.put(key, copier);
        }else{
            copier = BEAN_COPIERS.get(key);
        }
        copier.copy(source, target, null);
    }
    
    private static String genKey(Class<?> sourceClz, Class<?> targetClz){
        return sourceClz.toString() + targetClz.toString();
    }
    
    public static void main(String[] args) {
        Param1  p1 = new Param1();
        p1.setPrd("TOD");
        p1.setCntrctNm("you");
        p1.setOptnHdgSpotFwdIndctr("1");
        p1.setOptnSpotIndctr("0");
        p1.setIntrNlegBaseCcyTrdngDir("S");
        p1.setFrLegBaseAmnt(new BigDecimal(1500));
        p1.setFrLegTermAmnt(new BigDecimal(899));
        p1.setNrLegBaseAmnt(new BigDecimal(100));
        p1.setNrLegTermAmnt(new BigDecimal(56));
        p1.setIntrFlegBaseCcyDir("B");
        p1.setNlegSpotExchngRate(new BigDecimal(2));
        p1.setFlegSpotExchngRate(new BigDecimal(0.5));
        p1.setNrLegVlDt(new Date());
        p1.setFrLegVlDt(new Date());    
        p1.setNtngAmnt(new BigDecimal(2.3));
        p1.setClrngCcy("CNY");
        p1.setTcktTp("2");
        p1.setTcktId("6.25654313");
        Param2  p2 = new Param2();
        CacheBeanCopier.copy(p1, p2);
        System.out.println(new Gson().toJson(p2));
    }

}

本文最后才想起来,依赖也给大家贴出来:
  1. <dependency>  
  2.             <groupId>asm</groupId>  
  3.             <artifactId>asm</artifactId>  
  4.             <version>3.3.1</version>  
  5.         </dependency>  
  6.         <dependency>  
  7.             <groupId>asm</groupId>  
  8.             <artifactId>asm-commons</artifactId>  
  9.             <version>3.3.1</version>  
  10.         </dependency>  
  11.         <dependency>  
  12.             <groupId>asm</groupId>  
  13.             <artifactId>asm-util</artifactId>  
  14.             <version>3.3.1</version>  
  15.         </dependency>  
  16.         <dependency>  
  17.             <groupId>cglib</groupId>  
  18.             <artifactId>cglib-nodep</artifactId>  
  19.             <version>2.2.2</version>  
  20.         </dependency>  
 

今天突然想到这个问题就更新了下,有问题欢迎沟通交流     email: shubiao_dba@outlook.com

结合阿里代码规范约定+源码剖析属性拷贝性能安全问题

标签:contain   ace   private   slist   lis   字符串拼接   framework   base   大量   

原文地址:https://www.cnblogs.com/lhl-shubiao/p/copy.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!