Java属性拷贝——大道至简

  • Post author:
  • Post category:java


写业务代码的同学,一定经常使用一个API:

org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object)

从一个实例中拷贝属性值到另外一个实例(可以是相同class或不同class)

对于写业务代码同学来说,确实很“省”代码。员外近仨月就节省了很多代码。

例如,历史文章中有一篇,就是自己为了偷懒,写的一个工具方法:

传送门

昨天review同事的一段代码(压测报告响应时间长),业务逻辑非常简单:数据库查询数据(POJO List),然后copyProperties到VO List。也就是数据量会大一些(目前返回测试数据千条级别,企业级应用,单条数据的字段值也蛮多)。同事采用的是序列化与反序列化的方式进行属性copy的。

既然业务逻辑(不好意思称之为算法)简单,为什么会”慢”呢?

自己能想到对象(属性值)copy的方式有三种:

①:基于ObjectMapper做序列化与反序列化

source和target field大面积相同,例如一个dto对应的vo。

public class BeanUtils {
    private static Logger logger = LogManager.getLogger(CollectionsUtils.class);
    private static ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 从类实例中copy属性值
     * 并返回一个指定类的实例作为返回值
     * 基于{@link ObjectMapper}进行序列化与反序列化
     *
     * @param source
     * @param type
     * @param <T>
     * @return
     * @throws IOException
     */
    public static <T, E> T copyProperties(E source, Class<T> type) {
        String jsonString = null;
        try {
            jsonString = objectMapper.writeValueAsString(source);
            return objectMapper.readValue(jsonString, type);
        } catch (JsonProcessingException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException();
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            throw new RuntimeException();
        }
    }
}

②Spring中的接口:

org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object)

当然还有其它API,不做列举

③Getter/Setter方法使用

于是想着对三种方式进行实践测试:

public class User implements Cloneable{
    private String name;
    private String sex;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }

}
public class UserMirror implements Cloneable{
    private String name;
    private String sex;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }

}
public class BeanUtilsTest {
    private static final int count = 1000000;

    @Test
    public void copyProperties() throws IOException {
        System.out.println(String.format("拷贝数据:%d条", count));
        this.copyBySerialize();
        this.copyByReflex();
        this.copyByMethod();
    }

    private List<UserMirror> copyBySerialize() throws IOException {
        long begin = System.currentTimeMillis();
        User user = new User();
        user.setName("Young");
        user.setSex("male");
        List<UserMirror> list = new ArrayList<>(count);
        for (int index = 0; index < count; index++) {
            list.add(BeanUtils.copyProperties(user, UserMirror.class));
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("序列化反序列化方式耗时:%d millis", end - begin));
        return list;
    }

    private List<UserMirror> copyByReflex() throws IOException {
        long begin = System.currentTimeMillis();
        User user = new User();
        user.setName("Young");
        user.setSex("male");
        List<UserMirror> list = new ArrayList<>(count);
        for (int index = 0; index < count; index++) {
            UserMirror userMirror = new UserMirror();
            org.springframework.beans.BeanUtils.copyProperties(user, userMirror);
            list.add(userMirror);
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("反射方式耗时:%d millis", end - begin));
        return list;
    }

    /**
     * Getter/Setter
     *
     * @return
     * @throws IOException
     */
    private List<UserMirror> copyByMethod(){
        long begin = System.currentTimeMillis();
        User user = new User();
        user.setName("Young");
        user.setSex("male");
        List<UserMirror> list = new ArrayList<>(count);
        for (int index = 0; index < count; index++) {
            UserMirror userMirror = new UserMirror();
            userMirror.setName(user.getName());
            userMirror.setSex(user.getSex());
            list.add(userMirror);
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("Getter/Setter方式耗时:%d millis", end - begin));
        return list;
    }
}

然后来看看测试结果:

在这里插入图片描述

然后用数据来说说化:

从效率来看③优于②优于①,思考一下原因:

①为什么最慢?看看方法,细心点可以发现有一个IOException异常处理,因为序列化与反序列化是需要涉及到IO开销的。

②看看API的源码:其实质还是利用反射来调用Getter/Setter方法

/**
	 * Copy the property values of the given source bean into the given target bean.
	 * <p>Note: The source and target classes do not have to match or even be derived
	 * from each other, as long as the properties match. Any bean properties that the
	 * source bean exposes but the target bean does not will silently be ignored.
	 * @param source the source bean
	 * @param target the target bean
	 * @param editable the class (or interface) to restrict property setting to
	 * @param ignoreProperties array of property names to ignore
	 * @throws BeansException if the copying failed
	 * @see BeanWrapper
	 */
	private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
			@Nullable String... ignoreProperties) throws BeansException {

		Assert.notNull(source, "Source must not be null");
		Assert.notNull(target, "Target must not be null");

		Class<?> actualEditable = target.getClass();
		if (editable != null) {
			if (!editable.isInstance(target)) {
				throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
						"] not assignable to Editable class [" + editable.getName() + "]");
			}
			actualEditable = editable;
		}
		PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
		List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

		for (PropertyDescriptor targetPd : targetPds) {
			Method writeMethod = targetPd.getWriteMethod();
			if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
				PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
				if (sourcePd != null) {
					Method readMethod = sourcePd.getReadMethod();
					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);
							}
							writeMethod.invoke(target, value);
						}
						catch (Throwable ex) {
							throw new FatalBeanException(
									"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
						}
					}
				}
			}
		}
	}

③虽然代码量看起来多一点,可实质上就是方式②的一个详细解释。


当然呢,①和②更具通用性,③的效率就高一点。那么当一个产品的功能十分稳定以后,想要进行效率上的提升,那么①②替换为③,不失为一个思路。

总结一下:大道至简



版权声明:本文为u010730731原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。