在后台开发过程中我们肯定会使用各种各样的bean,我目前遇到2类
其一是dto:返回给调用方的bean
另外是entity:与数据库映射的bean
由此就会遇到bean直接赋值的问题,我们常常使用BeanUtils.copyProperties(a,b)来进行赋值,将a中的属性赋值给b中的属性(浅复制)
在最近的接口开发过程中遇到的场景:
前端传过来的数据包装成一个dto(a),然后new一个对应的entity(b),利用BeanUtils.copyProperties(a,b),将a中的数据传输给b,之后将b存储在数据库当中,这是非常简单的
我们再来看另外一个场景:
通常,a中的属性值会比b中少,因为,我们很多情况下不会把b中的所有数据都暴露给前端,这对于数据安全是十分重要的,也就是说,同样一个bean,a中的某个属性为null并不代表b中的该属性也是null,很有可能是有值的。
如果前端拿到一个dto,并且修改了其中的某个属性值,如果直接使用BeanUtils.copyProperties(a,b)来进行bean的相互赋值,就会造成b中的数据损失,持久层数据异常。
举例:
b{
user:job
password:123
age:29
}
前端拿到该bean,password属性被屏蔽,然后修改了年龄
a{
user:job
password:null
age:22
}
此时,我们需要将修改的年龄同步到数据库,如果直接使用BeanUtils.copyProperties(a,b)的话
b{
user:job
password:null
age:22
}
可以看到,数据库中b的密码已经为空了,这样你可就惨了~
那么,BeanUtils.copyProperties内部是如何实现这样的赋值功能的呢?我们看一下源码(a等同于source,b等同于target):
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
PropertyDescriptor[] var7 = targetPds;
int var8 = targetPds.length;
for(int var9 = 0; var9 < var8; ++var9) {
PropertyDescriptor targetPd = var7[var9];
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 var15) {
throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15);
}
}
}
}
}
PropertyDescriptor类表示JavaBean类通过存储器导出一个属性。主要方法:
1. getReadMethod(),获得用于读取属性值的方法
2. getWriteMethod(),获得用于写入属性值的方法
我们可以看到targetPds是一个PropertyDescriptor数组,长度就等于b的属性数目。
先循环,判断a中sourcePd是否为空(注意,不论a中该属性的值是否为空,只要有该属性就不为空)
然后把a中该属性的值赋给value,注意此时并没有判断value是否为空,所以就造成了上面我们所说持久层数据损失的情况了。