BeanCopier 與 BeanUtils 及 人工setter之間的比較

同事經過Jmeter壓測領券中心接口時發現了查詢店鋪券的一個性能瓶頸, 定位到瓶頸位於將entity list轉成model list處。由於領券中心需展現推薦店鋪的店鋪券,如一個100個店鋪每一個店鋪的可領店鋪券10個的話, 共有1000個店鋪券。這個數量級狀況下 經過BeanUtils.copyProperties的方式來自動轉化相比人工setter的話, 性能差了不少。以下所示git

使用BeanUtils轉化1000個對象

@Test
    public void test_convert_entity_to_model_performance_use_beanutils(){
        List<ShopCouponEntity> entityList  = Lists.newArrayList();
        for (int i = 0; i < 1000; i++) {
            ShopCouponEntity entity = new ShopCouponEntity();
            entityList.add(entity);
        }
        long start = System.currentTimeMillis();
        List<ShopCouponModel> modelList = new ArrayList<>();
        for (ShopCouponEntity src : entityList) {
            ShopCouponModel dest = new ShopCouponModel();
            BeanUtils.copyProperties(src, dest);
            modelList.add(dest);
        }
        System.out.printf("BeanUtils took time: %d(ms)%n",System.currentTimeMillis() - start);
    }

BeanUtils took time: 59(ms)github

手工setter

@Test
    public void test_convert_entity_to_model_performance_use_manually_setter(){
        List<ShopCouponEntity> entityList  = ...
        long start = System.currentTimeMillis();
        List<ShopCouponModel> modelList = new ArrayList<>();
        for (ShopCouponEntity src : entityList) {
            ShopCouponModel dest = new ShopCouponModel();
            dest.setCouponId(src.getCouponId());
            //...
            modelList.add(dest);
        }
        System.out.printf("manually setter take time: %d(ms)%n",System.currentTimeMillis() - start);
    }

manually setter take time: 3(ms)緩存

20倍的性能差距啊。app

以前同事推薦過BeanCopier 因而決定使用BeanCopier 看看性能表現工具

@Test
    public void test_convert_entity_to_model_performance_use_beancopier(){
        List<ShopCouponEntity> entityList  = ...
        long start = System.currentTimeMillis();
        BeanCopier b = BeanCopier.create(ShopCouponEntity.class, ShopCouponModel.class, false);
        List<ShopCouponModel> modelList = new ArrayList<>();
        for (ShopCouponEntity src : entityList) {
            ShopCouponModel dest = new ShopCouponModel();
            b.copy(src, dest, null);
            modelList.add(dest);
        }
        System.out.printf("BeanCopier took time: %d(ms)%n",System.currentTimeMillis() - start);
    }

BeanCopier took time: 10(ms)性能

相比BeanUtils也有6倍的性能提高。若是將生成的BeanCopier實例緩存起來 性能還有更大的提高 以下所示測試

BeanCopier b = getFromCache(sourceClass,targetClass); //從緩存中取
        long start = System.currentTimeMillis();
        List<ShopCouponModel> modelList = new ArrayList<>();
        for (ShopCouponEntity src : entityList) {
            ShopCouponModel dest = new ShopCouponModel();
            b.copy(src, dest, null);
            modelList.add(dest);
        }

BeanCopier from cache took time: 3(ms). 性能已經同人工setter了。優化

因而決定對BeanCopier進行封裝 便於平常開發使用 提供了以下的Apicode

public static <T> T copyProperties(Object source, Class<T> targetClass) ;
public static <T> List<T> copyPropertiesOfList(List<?> sourceList, Class<T> targetClass)

但這樣封裝的話 須要根據類信息經過反射建立一個對象 是否是也能優化呢?orm

直接new1000個對象

@Test
    public void test_batch_newInstance_just_new_object(){
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            ShopCouponModel ShopCouponModel = new ShopCouponModel();
        }
        System.out.printf("Just new object took time: %d(ms)%n",System.currentTimeMillis() - start);
    }

Just new object took time: 0(ms) 基本上是瞬間完成

經過反射建立1000個對象

@Test
    public void test_batch_newInstance_use_original_jdk(){
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            try {
                ShopCouponModel.class.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.printf("Original jdk newInstance took time: %d(ms)%n",System.currentTimeMillis() - start);
    }

Original jdk newInstance took time: 2(ms) 要慢一點了

github中找了一個相比jdk自帶的反射性能更高的工具reflectasm

@Test
    public void test_batch_newInstance_use_reflectasm(){
        ConstructorAccess<ShopCouponModel> access = ConstructorAccess.get(ShopCouponModel.class); //放在循環外面 至關於從緩存中獲取
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            ShopCouponModel ShopCouponModel = access.newInstance();
        }
        System.out.printf("reflectasm newInstance took time: %d(ms)%n",System.currentTimeMillis() - start);
    }

reflectasm newInstance took time: 0(ms) 基本上也是瞬間完成

最後對使用封裝後的BeanCopier作了測試

@Test
    public void test_convert_entity_to_model_performance_use_wrappedbeancopier(){
        List<ShopCouponEntity> entityList  = ...
        
        long start = System.currentTimeMillis();
        WrappedBeanCopier.copyPropertiesOfList(entityList, ShopCouponModel.class);
        System.out.printf("WrappedBeanCopier took time: %d(ms)%n",System.currentTimeMillis() - start);
    }

WrappedBeanCopier took time: 4(ms) 性能已經有極大的提高了

WrappedBeanCopier完整代碼

public class WrappedBeanCopier {
    private static final Map<String, BeanCopier> beanCopierCache = new ConcurrentHashMap<>();
    private static final Map<String,ConstructorAccess> constructorAccessCache = new ConcurrentHashMap<>();

    private static void copyProperties(Object source, Object target) {
        BeanCopier copier = getBeanCopier(source.getClass(), target.getClass());
        copier.copy(source, target, null);
    }

    private static BeanCopier getBeanCopier(Class sourceClass, Class targetClass) {
        String beanKey = generateKey(sourceClass, targetClass);
        BeanCopier copier = null;
        if (!beanCopierCache.containsKey(beanKey)) {
            copier = BeanCopier.create(sourceClass, targetClass, false);
            beanCopierCache.put(beanKey, copier);
        } else {
            copier = beanCopierCache.get(beanKey);
        }
        return copier;
    }

    private static String generateKey(Class<?> class1, Class<?> class2) {
        return class1.toString() + class2.toString();
    }

    public static <T> T copyProperties(Object source, Class<T> targetClass) {
        T t = null;
        try {
            t = targetClass.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(format("Create new instance of %s failed: %s", targetClass, e.getMessage()));
        }
        copyProperties(source, t);
        return t;
    }

    public static <T> List<T> copyPropertiesOfList(List<?> sourceList, Class<T> targetClass) {
        if (CollectionUtils.isEmpty(sourceList)) {
            return Collections.emptyList();
        }
        ConstructorAccess<T> constructorAccess = getConstructorAccess(targetClass);
        List<T> resultList = new ArrayList<>(sourceList.size());
        for (Object o : sourceList) {
            T t = null;
            try {
                t = constructorAccess.newInstance();
                copyProperties(o, t);
                resultList.add(t);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return resultList;
    }

    private static <T> ConstructorAccess<T> getConstructorAccess(Class<T> targetClass) {
        ConstructorAccess<T> constructorAccess = constructorAccessCache.get(targetClass.toString());
        if(constructorAccess != null) {
            return constructorAccess;
        }
        try {
            constructorAccess = ConstructorAccess.get(targetClass);
            constructorAccess.newInstance();
            constructorAccessCache.put(targetClass.toString(),constructorAccess);
        } catch (Exception e) {
            throw new RuntimeException(format("Create new instance of %s failed: %s", targetClass, e.getMessage()));
        }
        return constructorAccess;
    }

}

參考文檔

http://ysj5125094.iteye.com/b...

相關文章
相關標籤/搜索