同事通过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 entityList = Lists.newArrayList();
for (int i = 0; i < 1000; i++) {
ShopCouponEntity entity = new ShopCouponEntity();
entityList.add(entity);
}
long start = System.currentTimeMillis();
List 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 entityList = ...
long start = System.currentTimeMillis();
List 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 entityList = ...
long start = System.currentTimeMillis();
BeanCopier b = BeanCopier.create(ShopCouponEntity.class, ShopCouponModel.class, false);
List 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 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 copyProperties(Object source, Class targetClass) ;
public static List copyPropertiesOfList(List sourceList, Class 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 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 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 beanCopierCache = new ConcurrentHashMap<>();
private static final Map 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 copyProperties(Object source, Class 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 List copyPropertiesOfList(List sourceList, Class targetClass) {
if (CollectionUtils.isEmpty(sourceList)) {
return Collections.emptyList();
}
ConstructorAccess constructorAccess = getConstructorAccess(targetClass);
List 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 ConstructorAccess getConstructorAccess(Class targetClass) {
ConstructorAccess 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...