BeanUtils.copyProperties中的那些坑
1.null值会被拷贝
Model1
import java.time.LocalDateTime;
import java.util.List;
/**
* com.example
* Description:
*
* @author jack
* @date 2021/6/21 7:21 下午
*/
@Data
public class Model1 {
private String name;
private String password;
private int age;
private boolean vip;
private LocalDateTime dateTime;
private List<String> strList;
}
Model2
import java.time.LocalDateTime;
import java.util.List;
/**
* com.example
* Description:
*
* @author jack
* @date 2021/6/21 7:22 下午
*/
@Data
public class Model2 {
private String name;
private String password;
private Integer age;
private Boolean vip;
private LocalDateTime dateTime;
private List<Object> strList;
}
null值拷贝测试 BeanUtilTest
import org.springframework.beans.BeanUtils;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* com.example
* Description:
*
* @author jack
* @date 2021/6/21 7:24 下午
*/
public class BeanUtilTest {
public static void main(String[] args) {
Model1 model1 = new Model1();
model1.setName("张三");
model1.setAge(22);
List<String> strList = new ArrayList<>();
strList.add("我是张三");
model1.setStrList(strList);
Model2 model2 = new Model2();
model2.setDateTime(LocalDateTime.now());
BeanUtils.copyProperties(model1, model2);
System.out.println(model2);
}
}
model2中的dateTime属性变为了null
2.Boolean类型数据拷贝时可能会丢失
把Model1
和Model2
中的vip
字段改为isVip
,数据类型保持不变,一个为基本类型boolean
一个为包装类型Boolean
private boolean isVip;
测试用例不变
发现model1
中的isVip
并没有拷贝到model2
中
这里可能通过源码发现
// 通过targetPd.getName() 没有获取到 sourcePd,这里的targetPd.getName()为isVip
getPropertyDescriptor(source.getClass(), targetPd.getName())
接着往下面走
在org.springframework.beans.CachedIntrospectionResults#getPropertyDescriptor
中,通过target
对象中的isVip
属性去source
中查找时,并没有找到,导致没有拷贝,source
中的 isVip
字段被解析成了Vip
了
一直往下面走,走到java.beans.Introspector#getTargetPropertyInfo
中的513行
当属性类型为boolean
时,属性名以is
开头,属性名会去掉前面的is
所以 boolean
类型 isXXX()
会被解析为XXX
,拷贝的时候需要留意。
解决方法:
- 修改
Model2
中的private Boolean isVip
属性为Vip
,因为Model1
中的private boolean isVip
被解析为Vip
了 source
和target
的属性名称和属性类型保持一致
3. 泛型类型拷贝的问题
当target
中的属性名称和source
中的属性名称一致时,就会被拷贝,这个情况在开发中可能会出现问题,当使用target中的属性时,可能会报错java.lang.ClassCastException
解决方法:拷贝的的时候,忽略掉属性名称相同但类型不同的属性
BeanUtils
对org.springframework.beans.BeanUtils.copyProperties
进行了一下封装,忽略null值,忽略属性名称一样但类型不一样
package com.example;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.StringUtils;
import java.beans.PropertyDescriptor;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* com.youzan.fx.trade.test
* Description:
*
* @author jack
* @date 2021/6/21 5:07 下午
*/
public class BeanUtils {
private static final String BYTE = "byte";
private static final String SHORT = "short";
private static final String CHAR = "char";
private static final String BOOLEAN = "boolean";
private static final String INT = "int";
private static final String LONG = "long";
private static final String FLOAT = "float";
private static final String DOUBLE = "double";
/**
* 忽略null值,忽略属性名称一样但类型不一样
*
* @param source source
* @param target target
*/
public static void copyPropertiesIgnoreNull(Object source, Object target) {
org.springframework.beans.BeanUtils.copyProperties(source, target, getIgnoreFieldNames(source, target));
}
private static String[] getIgnoreFieldNames(Object source, Object target) {
final BeanWrapper sourceWrapper = new BeanWrapperImpl(source);
final BeanWrapper targetWrapper = new BeanWrapperImpl(target);
PropertyDescriptor[] sourcePds = sourceWrapper.getPropertyDescriptors();
PropertyDescriptor[] targetPds = targetWrapper.getPropertyDescriptors();
Map<String, String> targetMap = new HashMap<>(targetPds.length);
for (PropertyDescriptor targetPd : targetPds) {
targetMap.put(targetPd.getName(), getFieldTypeName(targetPd.getReadMethod().getGenericReturnType().getTypeName()));
}
Set<String> ignoreFieldSet = new HashSet<>();
for (PropertyDescriptor sourcePd : sourcePds) {
Object srcValue = sourceWrapper.getPropertyValue(sourcePd.getName());
if (srcValue == null) {
ignoreFieldSet.add(sourcePd.getName());
} else {
String fieldTypeName = targetMap.get(sourcePd.getName());
if (StringUtils.hasText(fieldTypeName) && !Objects.equals(fieldTypeName, getFieldTypeName(sourcePd.getReadMethod().getGenericReturnType().getTypeName()))) {
ignoreFieldSet.add(sourcePd.getName());
}
}
}
return ignoreFieldSet.toArray(new String[0]);
}
private static String getFieldTypeName(String fieldTypeName) {
String typeName = "";
switch (fieldTypeName) {
case BYTE:
typeName = Byte.class.getTypeName();
break;
case SHORT:
typeName = Short.class.getTypeName();
break;
case CHAR:
typeName = Character.class.getTypeName();
break;
case BOOLEAN:
typeName = Boolean.class.getTypeName();
break;
case INT:
typeName = Integer.class.getTypeName();
break;
case LONG:
typeName = Long.class.getTypeName();
break;
case FLOAT:
typeName = Float.class.getTypeName();
break;
case DOUBLE:
typeName = Double.class.getTypeName();
break;
default:
break;
}
if (StringUtils.hasText(typeName)) {
return typeName;
}
return fieldTypeName;
}
}
转载自:https://juejin.cn/post/6976243622890962952