注意!是浅拷贝!
Spring MVC处理信息时经常需要从 DO 转为 DTO,BeanUtils下的copyProperties() 是经常用到的方法。
之前也写过类似的工具类,现在来看 Spring 的具体实现。
示例代码
// 描述 复制给定源 Bean 的属性值至目标 Bean 中。 源和目标的类不需要完全匹配或互相派生,只需要属性相互匹配。 任何源 Bean 暴露但目标 Bean 不暴露的属性将会被忽视。 在 Spring 5.3 中,在匹配源对象和目标对象中的属性时,此方法遵循通用类型信息。
/**
* 定义三个类,并调用 copyProperties 查看结果
* 这里省略 toString() 方法
*/
@Data
public class A {
private String name;
private String age;
private String tel;
}
@Data
public class B {
private String name;
private int age;
private Tel tel;
}
@Data
public class Tel {
String telPhone;
}
public class test {
public static void main(String[] args) {
A a = new A();
a.setName("ANAME");
a.setAge("10");
a.setTel("10086");
B b = new B();
BeanUtils.copyProperties(a, b);
System.out.println(a);
System.out.println(b);
}
}
结果:
A{name='ANAME', age='10', tel='10086'}
B{name='ANAME', age=0, tel=null}
看起来 copyProperties 只会复制类型和名称相同的属性值,下面进入源码。
源码注释
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 类型。 Class<?> actualEditable = target.getClass(); //editable 不为空且 target 不为 editable 实例,抛出异常 if (editable != null) { if (!editable.isInstance(target)) { throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]"); } //editable 不为空,则作为目标对象的 Class 类型 actualEditable = editable; } /** * PropertyDescriptor,属性都获取对应的描述器,内部存储了读写方法的名称 * ,读写操作方法的 MethodRef 和一些引用。 * MethodRef 也属于方法和类型的引用包装类(软引用和弱引用) * 通过 getPropertyDescriptors() 方法,可以通过 Class 给出的信息去检索对应类 * 的 PropertyDescriptor */ 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) { //处理类型转换 ResolvableType sourceResolvableType = ResolvableType.forMethodReturnType(readMethod); ResolvableType targetResolvableType = ResolvableType.forMethodParameter(writeMethod, 0); //如果任一 ResolvableType 具有不可解析的泛型,则忽略可分配检查中的泛型类型 boolean isAssignable = (sourceResolvableType.hasUnresolvableGenerics() || targetResolvableType.hasUnresolvableGenerics() ? ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) : targetResolvableType.isAssignableFrom(sourceResolvableType)); if (isAssignable) { try { //读方法不为 public,设置 Accessible 强制调用 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); } } } } } } }
显而易见,copyProperties() 同样依赖于 getter-setter 进行取值和注入。如果缺失 get 或 set 方法就会导致该方法忽视对应的属性。