最近开发了一个数据同步的功能,需要进行对象的拷贝,由于对象拷贝的工具的原理和细节不了解,踩了几个坑,在这里跟大家分享一下。
public static void main(String[] args){
SourceDTO sourceDTO = new SourceDTO();
sourceDTO.setSex("man");
sourceDTO.setCreateTime("2022-02-12 12:00:00");
SysNetworkDTO sysNetworkDTO = new SysNetworkDTO();
List<SysNetworkDTO> list = Lists.newArrayList();
list.add(sysNetworkDTO);
//SourceDTO中的list中的数据为SysNetworkDTO类型
sourceDTO.setList(list);
TargetDTO targetDTO = new TargetDTO();
BeanUtils.copyProperties(sourceDTO,targetDTO);
//TargetDTO中的list中的数据为SysNetworkVO类型
if(CollectionUtils.isNotEmpty(targetDTO.getList())){
for(SysNetworkVO sysNetworkVO:targetDTO.getList()){
}
}
}
代码执行到上面的第15行时,报错"main"
java.lang.ClassCastException: com.netty.use.nettyuse.copy.SysNetworkDTO cannot be cast to com.netty.use.nettyuse.copy.SysNetworkVO,类型转换错误。
错误的根源:
BeanUtils工具是浅拷贝,对于对象中嵌套的List属性,指针还是指向原来的内存空间,并没有创建新的对象出来。对象targetDTO的list属性值,在拷贝的时候,只是把指针指向了List的内存空间,list里面数据本质上是SysNetworkDTO类型。所以我们循环遍历的时候,就出现了类型无法转化错误。
很多同学会说,那在测试环境没有测试出来这个问题吗,测试环境还真没这个问题。因为测试环境中,我的源数据是从数据中读取的,list属性值为null,而线上有部分数据list属性值非null,就导致上线后有值的那部分数据处理异常了。
下面看下BeanUtils和Orika的一些区别
1.原始对象和目标对象,属性名称相同,类型不同
原始类:
@Data
public class SourceDTO {
private String name;
private String sex;
private String createTime;
private SysNetworkDTO sysNetwork;
private List<SysNetworkDTO> list;
}
目标类:
@Data
public class TargetDTO {
private String name;
private Integer age;
private LocalDateTime createTime;
private SysNetworkVO sysNetwork;
private List<SysNetworkVO> list;
}
我们使用下面的代码测试下
SourceDTO source = new SourceDTO();
source.setName("李太白");
source.setSex("1k");
TargetDTO targetDTO = orikaBeanMapper.map(source, TargetDTO.class);
System.out.println(JsonUtils.toJson(targetDTO));
打印结果:{"name":"李太白"},虽然sex字段在两个类中不一致,拷贝并没有报错,值也没有拷贝上。
我们在看下给createTime字段赋值之后,再拷贝会是什么后果:
SourceDTO source = new SourceDTO();
source.setName("李太白");
source.setSex("1k");
source.setCreateTime("2022-12-22 09:09:09");
TargetDTO targetDTO = orikaBeanMapper.map(source, TargetDTO.class);
System.out.println(JsonUtils.toJson(targetDTO));
拷贝时报错:
ma.glasnost.orika.MappingException: No converter registered for conversion from String to LocalDateTime, nor any ObjectFactory which can generate LocalDateTime from String
我们再换成Spring的BeanUtils工具试试:
TargetDTO targetDTO = new TargetDTO();
BeanUtils.copyProperties(source, targetDTO);
打印结果:{"name":"李太白"},能够正常打印,类型不一致的属性值,不会拷贝。
那我们再看下嵌套对象的情况:
SourceDTO source = new SourceDTO();
source.setName("李太白");
source.setSex("1k");
List<SysNetworkDTO> list = Lists.newArrayList();
SysNetworkDTO sysNetworkDTO = new SysNetworkDTO();
sysNetworkDTO.setId(1);
sysNetworkDTO.setName("网络");
source.setSysNetwork(sysNetworkDTO);
list.add(sysNetworkDTO);
source.setList(list);
TargetDTO targetDTO = new TargetDTO();
BeanUtils.copyProperties(source, targetDTO);
System.out.println(JsonUtils.toJson(targetDTO));
TargetDTO targetDTO2 = orikaBeanMapper.map(source, TargetDTO.class);
System.out.println(JsonUtils.toJson(targetDTO2));
System.out.println(targetDTO2.getSysNetwork().getName());
BeanUtils打印结果:{"list":[{"id":1,"name":"网络"}],"name":"李太白"}
orika拷贝后的打印结果:{"list":[{"id":1,"name":"网络"}],"name":"李太白","sysNetwork":{".list[0]"}}
从打印结果,我们可以得出一个结论:BeanUtils对于属性名称相同,但是类型不同的属性,属性值拷贝不上,但不会报错。orika对于属性名称相同,但是类型不同的属性,会尝试拷贝,但可能会因为没有对应的转化器而报错。