zoukankan      html  css  js  c++  java
  • 结合阿里代码规范约定+源码剖析属性拷贝性能安全问题

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

    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|
    ").append("|----|----|----|----|----|----|
    ");
    
        @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("
    ");
        }
    
        @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

  • 相关阅读:
    ANDROIDSTUDIO手动安装插件
    xcode 升级到最新的11.1版本打开项目卡顿解决方案
    OC各种数据类型之间的转换方法
    TOJ 3365 ZOJ 3232 It's not Floyd Algorithm / 强连通分量
    在linux下makefile的使用
    Binary Search二分法搜索C++程序
    ORA-01654错误
    合作版状态模式之设计
    基于FPGA的超声波测距(一)
    如何随机获取数据库不连续ID的数据?
  • 原文地址:https://www.cnblogs.com/lhl-shubiao/p/copy.html
Copyright © 2011-2022 走看看