org.springframework.beans.BeanUtils
存在的问题:
会将源对象中字段为null的值,覆盖到目标有 值字段。
如下代码:
public class Test {
public static void main(String []args){
Demo demo=new Demo();
demo.a="aaa";
demo.b="bbb";
Demo demo1=new Demo();
demo1.c="ccc";
BeanUtils.copyProperties(demo, demo1);
System.out.println("demo:"+demo.toString());
System.out.println("demo1:"+demo1.toString());
}
static class Demo{
public String a;
public String b;
public String c;
public String getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
public String getC() {
return c;
}
public void setC(String c) {
this.c = c;
}
@Override
public String toString() {
return "Demo{" +
"a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
'}';
}
}
}
运行结果:
demo:Demo{a=‘aaa’, b=‘bbb’, c=‘null’}
demo1:Demo{a=‘aaa’, b=‘bbb’, c=‘null’}
源码分析:
private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
throws BeansException {
// 检查source和target对象是否为null,否则抛运行时异常
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
// 获取target对象的类信息
Class<?> actualEditable = target.getClass();
// 若editable不为null,检查target对象是否是editable类的实例,若不是则抛出运行时异常
// 这里的editable类是为了做属性拷贝时限制用的
// 若actualEditable和editable相同,则拷贝actualEditable的所有属性
// 若actualEditable是editable的子类,则只拷贝editable类中的属性
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
// 获取目标类的所有PropertyDescriptor,getPropertyDescriptors这个方法请看下方
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
// 获取该属性对应的set方法
Method writeMethod = targetPd.getWriteMethod();
// 属性的set方法存在 且 该属性不包含在忽略属性列表中
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
// 获取source类相同名字的PropertyDescriptor, getPropertyDescriptor的具体实现看下方
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
// 获取对应的get方法
Method readMethod = sourcePd.getReadMethod();
// set方法存在 且 target的set方法的入参是source的get方法返回值的父类或父接口或者类型相同
// 具体ClassUtils.isAssignable()的实现方式请看下面详解
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
//get方法是否是public的
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
//暴力反射,取消权限控制检查
readMethod.setAccessible(true);
}
//获取get方法的返回值
Object value = readMethod.invoke(source);
// 原理同上
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
// 将get方法的返回值 赋值给set方法作为入参
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
解决方案:
public static String[] getNullPropertyNames (Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<String>();
for(java.beans.PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) emptyNames.add(pd.getName());
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
public static void copyPropertiesIgnoreNull(Object src, Object target){
BeanUtils.copyProperties(src, target, getNullPropertyNames(src));
}
然后在调用这个方法进行复制:
BeanUtils.copyProperties(examLifeStyle, examDetail, getNullPropertyNames(examLifeStyle));
延伸拓展:
BeanUtils常用方法
1 populate(Object bean, Map<String,String[]>properties)
将Map数据封装到指定Javabean中,一般用于将表单的所有数据封装到javabean
2 setProperty(Object obj,String name,Object value)
设置属性值
3 getProperty(Object obj,String name)
获得属性值