注意!是浅拷贝!
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 方法就会导致该方法忽视对应的属性。