前言
在 Java 开发中,很多时候需要将两个属性基本相同的对象进行属性复制,比如 DO 转 VO等等。
本文主要介绍自己实现的简易拷贝工具类与 Spring 提供的属性拷贝的对比。
Spring 提供的属性拷贝
在 Spring 中直接调用 BeanUtils.copyProperties();即可。
它的核心通过循环 target 的所有方法名,然后在 source 中找到对应的方法名,最后通过反射从 source 中获取并写入 target 中。
Spring 没有通过 java.lang.reflect 中的 Field 来做,而是通过 java.beans 中的 PropertyDescriptor 来实现。
补充:PropertyDescriptor 描述 Java Bean 通过一对存储器方法导出的一个属性。
通过 PropertyDescriptor 提供的 getReadMethod() 和 getWriteMethod() 方法,可以方便的获取到读取、写入属性值的方法(Method)。
同时,Spring 也做了缓存,在测试中,第一次的对象拷贝用时 300+ 毫秒,之后在缓存中获取,用时 0 毫秒。
源码如下图所示:
缓存源码:
自己写的简易版
1 public static void copyBean(Object target, Object source) throws IllegalAccessException, InstantiationException, NoSuchFieldException { 2 Class targetClass = target.getClass(); 3 Class sourceClass = source.getClass(); 4 // 获取目标类的所有参数 Field 5 Field[] fields = targetClass.getDeclaredFields(); 6 // 为目标类的每个参数设值 7 for (Field field : fields) { 8 // 如果数据源对象中存在对应的参数 9 Field sourceField = sourceClass.getDeclaredField(field.getName());10 if (null != sourceField) {11 Field targetField = targetClass.getDeclaredField(field.getName());12 targetField.setAccessible(true);13 sourceField.setAccessible(true);14 targetField.set(target, sourceField.get(source));15 }16 }17 }18 19 // 类似 Spring 的版本20 public static void copyBeanByMethod(Object target, Object source) throws IntrospectionException, InvocationTargetException, IllegalAccessException {21 Class targetClass = target.getClass();22 Class sourceClass = source.getClass();23 Field[] sourceFields = sourceClass.getDeclaredFields();24 for (Field field : sourceFields) {25 PropertyDescriptor targetProperty;26 try {27 targetProperty = new PropertyDescriptor(field.getName(), targetClass);28 } catch (IntrospectionException e) {29 continue;30 }31 Method writeMethod = targetProperty.getWriteMethod();32 if (writeMethod != null) {33 PropertyDescriptor sourceProperty = new PropertyDescriptor(field.getName(), sourceClass);34 Method readMethod = sourceProperty.getReadMethod();35 if (!Modifier.isPublic(readMethod.getModifiers())) {36 readMethod.setAccessible(true);37 }38 // 读取 source 中属性的值39 Object value = readMethod.invoke(source);40 if (!Modifier.isPublic(writeMethod.getModifiers())) {41 writeMethod.setAccessible(true);42 }43 // 为 target 对应的属性赋值44 writeMethod.invoke(target, value);45 }46 }47 }
小结
Spring 所提供的属性拷贝虽然第一次效率较低,但随后如果再次使用相同的 source 进行拷贝,则 Spring 会通过第一次拷贝保存的缓存来直接进行快速的拷贝
参考资料
[1]