BeanCopier 与 BeanUtils 及 人工setter之间的比较

同事通过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;
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果源对象和目标对象的属性名称不同,并且属性类型也不同,可以使用BeanUtils.copyProperties(source, target, converter)方法,其中converter参数是一个自定义的属性转换器(Converter),用于实现不同属性名之间的属性拷贝。具体实现方法如下: 1. 定义一个源对象和目标对象,属性名称和类型不同: ```java public class Source { private String name; private int age; // getter和setter方法省略 } public class Target { private String username; private Integer userAge; // getter和setter方法省略 } ``` 2. 定义一个自定义的属性转换器: ```java public class CustomConverter implements Converter { @Override public Object convert(Class clazz, Object value) { if (clazz == String.class && value instanceof Integer) { return String.valueOf(value); } else if (clazz == Integer.class && value instanceof String) { return Integer.parseInt((String) value); } return value; } } ``` 上述自定义的属性转换器实现了将Integer类型转换成String类型,以及将String类型转换成Integer类型的功能。 3. 在使用BeanUtils.copyProperties()时,指定converter参数: ```java public static void main(String[] args) { Source source = new Source(); source.setName("Tom"); source.setAge(18); Target target = new Target(); BeanUtils.copyProperties(source, target, new CustomConverter()); System.out.println(target.getUsername()); // Tom System.out.println(target.getUserAge()); // 18 } ``` 在上述代码中,我们通过自定义的属性转换器实现了将源对象的"name"属性拷贝到目标对象的"username"属性中,并且将源对象的"age"属性拷贝到目标对象的"userAge"属性中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值