同事經過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; } }