测试代码

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

  1. 根据bean class获取缓存中的CachedIntrospectionResults,如果不存在则创建并缓存
  2. spring工厂中加载BeanInfoFactory实现类,目前仅一种实现:ExtendedBeanInfoFactory
  3. 委派java bean内省类java.beans.Introspector#getBeanInfo(java.lang.Class<?>)获取BeanInfo,并封装为:ExtendedBeanInfo
  4. java.beans.Introspector#getBeanInfo()内省类根据bean class类构建BeanDescriptor,类public修饰方法前缀set,get构建MethodDescriptor。类属性构建PropertyDescriptor。

底层spring通过反射调用写入方法复制属性,通过java bean内省类获取java bean属性、方法描述。虽然spring使用各种缓存优化获取java bean写入属性的方法,以及JVM自身对反射调用的优化,性能仍不及cglib,且不是一个数量级。继续学习咯

cglib BeanCopier源码学习

  1. 构建BeanCopier
  2. 复制属性

查看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提供的工具类

总结

常见属性复制工具类性能由高到低排序

  1. cglib BeanCopier
  2. spring BeanUtils
  3. Apache BeanUtils(10W次以内性能略高与PropertyUtils)
  4. Apache PropertyUtils