详谈 JAVA对象拷贝
引出
对象拷贝对于开发说书司空见惯,java从工具类到框架,对于这个功能都有实现的方法,一下就是实现的几种方法。
方法一
原始的get、set方式
我是比较常用这个做法的,比如现在DemoDTO
删去某个字段,编译器就会报错,就会引起你的注意了,让问题提前暴露。
DemoDTO demoDTO = new DemoDTO();
demoDTO.setId(RandomUtil.uuId());
demoDTO.setOrganizeId(demoEntity.getOrganizeId());
demoDTO.setObjectType(demoEntity.getPosition());
demoDTO.setObjectId(demoEntity.positionId());
缺点大概就是,显得代码非常冗余。确实好修改。
方法二(不推荐)
很多主流的java工具包中的 BeanUtil.copyProperties()
比较知名的大概就是hutool了。
// 前端传输的对象
DiagramDTO diagramDTO = new DiagramDTO();
// 如果前端传入的id事包含e的,升级后就会报错
diagramDTO.setId("xxxx");
diagramDTO.setCode("encodeXXXX");
diagramDTO.setName("图表");
Diagram diagram = new Diagram();
// 关键点,数据拷贝
BeanUtil.copyProperties(diagramDTO, diagram);
System.out.println("数据实体对象:" + diagram);
相信这个算是很主流的一个写法了,但事实上包含了一些较为严重的缺陷。
在早期的5.7.2版本之前的Hutool中,虽然字段类型不一样,但是做了兼容处理,所以没有影响业务逻辑。
举个例子:
// DTO中
private String id;
// Entity中
private Integer id;
此时是没有问题可以转换的。
升级后,执行报错,因为升级后的版本修改了实现,增加了下面的逻辑,如果包含E, 就会抛错,从而影响了业务逻辑,同时这个id是否包含e又是随机因素。
if(StrUtil.containsIgnoreCase(number,"E")){
// 科学计数法忽略支持,科学计数法一般用于表示非常小和非常大的数字,这类数字转换为int后精度丢失,没有意义。
throw new NumberFormatException(StrUtil.format("Unsupported int format:[{}]"number));
}
从而,团队中的某些人偷偷改了数据传输对象DTO,比如修改了类型、删去了某个字段。用BeanUtil.copyProperties
的方式压根无法在编译阶段发现,更别提修改的影响范围了,这就只能把风险暴露到生产上去了
现在我们怎么做的?
方法三 (MapStruct)
MapStruct也是Java中另外一个用于映射对象很流行的开源工具。它是在编译阶段生成对应的映射代码,相对于ModelMapper底层映射的方案,性能更好。也保留了在编译时暴露映射错误的代码,让错误提前暴露。
// 使用了`@Mapper`注解,用来表明使用`MapStruct`处理
@Mapper
public interface MouldInfoConvert {
MouldInfoConvert INSTANCE = Mappers.getMapper(MouldInfoConvert.class);
MouldInfo convert(MouldInfoDto mouldInfoDto);
MouldInfoDto convert(MouldInfo mouldInfo);
List<MouldInfoDto> convert2DtoList(List<MouldInfo> list);
List<MouldInfo> convert2EntityList(List<MouldInfoDto> list);
}
// 使用
List<MouldInfoDto> mouldInfoDtos = MouldInfoConvert.INSTANCE.convert2DtoList(list);
MapStruct的本质就是在编译时生成相应的实现方法(get/set)
还有许多高级的用法,例如不同名字的字段的映射,添加自定义方法,多个源参数映射等等。
多说一句 One more thing
IDEA中可以直接安装Mapstruct Support插件
- 突出显示目标属性和源属性。将目标属性和源属性转到声明的setter / getter中;
- 错误和快速修复:
- 缺少@Mapper或@MapperConfig注解检查;
- 快速修复未映射的目标属性,添加未映射目标属性和忽略未映射目标属性;