同事经过Jmeter压测领券中心接口时发现了查询店铺券的一个性能瓶颈, 定位到瓶颈位于将entity list
转成model list
处。由于领券中心需展现推荐店铺的店铺券,如一个100个店铺每一个店铺的可领店铺券10个的话, 共有1000个店铺券。这个数量级状况下 经过BeanUtils.copyProperties
的方式来自动转化相比人工setter的话, 性能差了不少。以下所示git
@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
@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
@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) 基本上是瞬间完成
@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; } }