测试代码
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.beans.BeanCopier;
/**
* @author 会灰翔的灰机
*/
public class BeanUtilsTest {
public static void main(String[] args) throws Exception {
test(10000);
test(100000);
test(1000000);
}
public static void test(int count) throws Exception {
MyBean source = new MyBean();
MyBean target = new MyBean();
source.setByt((byte) 1);
source.setBigDecimal(new BigDecimal("1.1"));
source.setCharact('Z');
source.setDoub(3.0d);
source.setFloa(9.9f);
source.setInteg(123);
source.setLon(99L);
source.setShortNum((short) 9);
source.setStr("my bean");
source.setDate(new Date());
copy4SpringBeanUtils(count, source, target);
copy4CglibBeanCopier(count, source, target);
copy4ApacheBeanUtils(count, source, target);
copy4ApachePropertyUtils(count, source, target);
System.out.println("--##----##----##----##----##----##----##----##----##--");
}
public static void copy4SpringBeanUtils(int count, MyBean source, MyBean target) {
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
BeanUtils.copyProperties(source, target);
}
System.out.println("copy4SpringBeanUtils cost = " + (System.currentTimeMillis() - start));
}
public static void copy4CglibBeanCopier(int count, MyBean source, MyBean target) {
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
BeanCopier.create(source.getClass(), target.getClass(), false).copy(source, target, null);
}
System.out.println("copy4CglibBeanCopier cost = " + (System.currentTimeMillis() - start));
}
public static void copy4ApacheBeanUtils(int count, MyBean source, MyBean target)
throws InvocationTargetException, IllegalAccessException {
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
org.apache.commons.beanutils.BeanUtils.copyProperties(target, source);
}
System.out.println("copy4ApacheBeanUtils cost = " + (System.currentTimeMillis() - start));
}
public static void copy4ApachePropertyUtils(int count, MyBean source, MyBean target)
throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
PropertyUtils.copyProperties(target, source);
}
System.out.println("copy4ApachePropertyUtils cost = " + (System.currentTimeMillis() - start));
}
@Data
private static class MyBean implements Serializable {
private Integer integ;
private Double doub;
private BigDecimal bigDecimal;
private String str;
private Long lon;
private Character charact;
private Float floa;
private Byte byt;
private Short shortNum;
private Date date;
}
}
测试报告
统计单位:毫秒
注意:不能一次性执行测试代码,底层优化会影响测试结果,一个个单独运行
1W次复制
copy4SpringBeanUtils cost = 2364
copy4CglibBeanCopier cost = 212
copy4ApacheBeanUtils cost = 1582
copy4ApachePropertyUtils cost = 1272
10W次复制
copy4SpringBeanUtils cost = 2611
copy4CglibBeanCopier cost = 296
copy4ApacheBeanUtils cost = 8905
copy4ApachePropertyUtils cost = 4680
100W次复制
copy4SpringBeanUtils cost = 5227
copy4CglibBeanCopier cost = 526
copy4ApacheBeanUtils cost = 50343
copy4ApachePropertyUtils cost = 62017
为什么Spring与Cglib这么快?
spring BeanUtils源码学习
一句话总结:使用反射调用进行属性拷贝,那么为什么性能依然那么好?当然是由于JVM的底层优化咯,JVM底层对于反射热点调用会自动优化为字节码调用。那么来看下spring的源码做了哪些处理?
// org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object, java.lang.Class<?>, java.lang.String...)
// 1. 源bean。2. 目标bean
// 3. 通过editable class获取哪些属性需要copy并写入(存在写入方法的bean属性)
// 4. 需要忽略拷贝的属性
private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
throws BeansException {
...
// 1. 如果存在editable则校验它的类型并使用它进行拷贝
Class<?> actualEditable = target.getClass();
if (editable != null) {
// editable类型校验
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
// 2. 根据目标class类获取java bean属性描述:java.beans.PropertyDescriptor
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
// 3. 遍历目标java bean属性描述进行拷贝
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
// 4. 目标类存在属性值的写入方法并且不死忽略掉的属性
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
5. 根据属性名称获取源java bean的属性描述
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
6. 源bean属性读取方法的返回值类型与目标bean属性写入方法的第一个入参类型一致
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
7. 反射调用执行拷贝
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
获取bean属性描述:org.springframework.beans.BeanUtils#getPropertyDescriptor
- 根据bean class获取缓存中的CachedIntrospectionResults,如果不存在则创建并缓存
- spring工厂中加载BeanInfoFactory实现类,目前仅一种实现:ExtendedBeanInfoFactory
- 委派java bean内省类java.beans.Introspector#getBeanInfo(java.lang.Class<?>)获取BeanInfo,并封装为:ExtendedBeanInfo
- java.beans.Introspector#getBeanInfo()内省类根据bean class类构建BeanDescriptor,类public修饰方法前缀set,get构建MethodDescriptor。类属性构建PropertyDescriptor。
底层spring通过反射调用写入方法复制属性,通过java bean内省类获取java bean属性、方法描述。虽然spring使用各种缓存优化获取java bean写入属性的方法,以及JVM自身对反射调用的优化,性能仍不及cglib,且不是一个数量级。继续学习咯
cglib BeanCopier源码学习
- 构建BeanCopier
- 复制属性
查看cglib生成BeanCopier复制类代码
import com.dianwoba.wireless.rider.gateway.rider.info.BeanUtilsTest.MyBean;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.core.Converter;
public class BeanUtilsTest$MyBean$$BeanCopierByCGLIB$$b632df40 extends BeanCopier {
public BeanUtilsTest$MyBean$$BeanCopierByCGLIB$$b632df40() {
}
public void copy(Object var1, Object var2, Converter var3) {
MyBean var10000 = (MyBean)var2;
MyBean var10001 = (MyBean)var1;
var10000.setBigDecimal(((MyBean)var1).getBigDecimal());
var10000.setByt(var10001.getByt());
var10000.setCharact(var10001.getCharact());
var10000.setDate(var10001.getDate());
var10000.setDoub(var10001.getDoub());
var10000.setFloa(var10001.getFloa());
var10000.setInteg(var10001.getInteg());
var10000.setLon(var10001.getLon());
var10000.setShortNum(var10001.getShortNum());
var10000.setStr(var10001.getStr());
}
}
cglib生成的源码就跟我们自己写的代码复制属性一毛一样,所以如果有bean copy属性还是优先使用cglib提供的工具类
总结
常见属性复制工具类性能由高到低排序
- cglib BeanCopier
- spring BeanUtils
- Apache BeanUtils(10W次以内性能略高与PropertyUtils)
- Apache PropertyUtils