前言
在 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] 谈谈 Java 开发中的对象拷贝