5

同事通过Jmeter压测领券中心接口时发现了查询店铺券的一个性能瓶颈, 定位到瓶颈位于将entity list转成model list处。因为领券中心需展示推荐店铺的店铺券,如一个100个店铺每个店铺的可领店铺券10个的话, 共有1000个店铺券。这个数量级情况下 通过BeanUtils.copyProperties的方式来自动转化相比人工setter的话, 性能差了很多。如下所示

使用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)

手工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倍的性能差距啊。

之前同事推荐过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进行封装 便于日常开发使用 提供了如下的Api

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

但这样封装的话 需要根据类信息通过反射创建一个对象 是不是也能优化呢?

直接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...


zhuguowei2
825 声望26 粉丝

引用和评论

0 条评论