正确地使用 BeanUtils.copyProperties

我是 ABin-阿斌:写一生代码,创一世佳话,筑一览芳华。如果小伙伴们觉得不错就一键三连吧~

在这里插入图片描述
声明:原作者:微信公众号:JavaGuide

文章目录

    • 1 . 使用 BeanUtils.copyProperties 转换
    • 2 . 踩坑经历
      • 2.1 包装类型转基本类型问题
      • 2.2 空格问题
      • 2.3 查找不到字段引用
      • 2.4 前端误传字段,直接把数据库覆盖了
    • 3 . 插件推荐
    • 4 . 更好的选择

  • 程序员的日常搬砖,一旦涉及到业务功能开发,相信对象转换的操作,你一定不会陌生,比如项目中DO、DTO、VO对象,一个需求写下来,总是需要那么三五七八次的转换。

举个例子,假设现在有个 OrderDTO ,定义如下所示:

public class OrderDTO {  
    private long id;  
  
    private Long userId;  
  
    private String orderNo;  
  
    private Date gmtCreated;  
  
    // 省略get、set方法  
}  

有个 OrderVO,定义如下所示:

public class OrderVO {  
    private long id;  
  
    private long userId;  
  
    private String orderNo;  
  
    private Date gmtCreated;  
  
   // 省略get、set方法  
}  

如果不使用任何转换工具,代码是下面这样的:

public static void main(String[] args) {  
    OrderDTO orderDTO = new OrderDTO();  
    orderDTO.setId(1L);  
    orderDTO.setUserId(123L);  
    orderDTO.setOrderNo("20210518000001");  
    orderDTO.setGmtCreated(new Date());  
  
    OrderVO orderVO = new OrderVO();  
    orderVO.setId(orderDTO.getId());  
    orderVO.setUserId(orderDTO.getUserId());  
    orderVO.setOrderNo(orderDTO.getOrderNo());  
    orderVO.setGmtCreated(orderDTO.getGmtCreated());  
  
    System.out.println(orderVO.getId());  
    System.out.println(orderVO.getUserId());  
    System.out.println(orderVO.getOrderNo());  
    System.out.println(orderVO.getGmtCreated());  
}  

运行结果:

1 . 使用 BeanUtils.copyProperties 转换

  • 因为项目中类似上面的转换多而繁琐,所以很多公司的项目中会使用 Spring 框架里的BeanUtils.copyProperties来做对象转换,代码如下所示:
OrderVO orderVO = new OrderVO();  
BeanUtils.copyProperties(orderDTO, orderVO);  
  • 一行代码搞定,很方便,运行结果也和原来一模一样。

  • 不过这个工具带来便利的同时,也带来了很多问题,稍微不注意就会踩坑,接下来就总结下使用这个工具常见的几个坑。

2 . 踩坑经历

2.1 包装类型转基本类型问题

java.lang.IllegalArgumentException

细心的你可能会发现,OrderDTO 中的 userId 字段,我定义的是 Long 类型:

正确地使用 BeanUtils.copyProperties_第1张图片

OrderVO 中的 userId 字段,我定义的是 long 类型:

正确地使用 BeanUtils.copyProperties_第2张图片

然后我们运行下下面所示的代码:

public static void main(String[] args) {  
    OrderDTO orderDTO = new OrderDTO();  
    orderDTO.setId(1L);  
    orderDTO.setUserId(null);  
    orderDTO.setOrderNo("20210518000001");  
    orderDTO.setGmtCreated(new Date());  
  
    OrderVO orderVO = new OrderVO();  
    BeanUtils.copyProperties(orderDTO, orderVO);  
}  

会看到代码抛了java.lang.IllegalArgumentException异常:

2.2 空格问题

  • 假设 OrderVOorderNo 字段,是用户自定义的,用户不小心输入了空格,使用BeanUtils.copyProperties后,空格会带入到 OrderDTOorderNo 字段,如果不小心,就会把脏数据落到数据库(而我们希望的是去除空格再落库的),造成一系列后续问题:
public static void main(String[] args) {  
    OrderVO orderVO = new OrderVO();  
    orderVO.setId(1L);  
    orderVO.setUserId(123L);  
    // 模拟空格场景  
    orderVO.setOrderNo(" 20210518000001 ");  
    orderVO.setGmtCreated(new Date());  
  
    OrderDTO orderDTO = new OrderDTO();  
    BeanUtils.copyProperties(orderVO, orderDTO);  
  
    System.out.println(orderDTO.getOrderNo());  
}  

运行结果:

2.3 查找不到字段引用

  • 使用BeanUtils.copyProperties后,会看到字段并没有引用,其实是有用到的,如下图所示:

正确地使用 BeanUtils.copyProperties_第3张图片

  • 有些小伙伴在看代码时,看到字段没有地方引用,可能就忍不住想删掉,结果就导致真正使用该字段的地方取不到值,产生 bug。

2.4 前端误传字段,直接把数据库覆盖了

  • 如果接口定义的比较严谨,理论上是不应该存在这种情况的,不过凡事总有特殊,这里举个接口不严谨导致数据被覆盖的例子。

假如 OrderVOOrderDTO 有如下 2 个字段:

/**  
 * 已收金额  
 * 单位:分  
 */  
private Long receivedAmount;  
  
/**  
 * 备注  
 */  
private String remark;  
  • 正常情况下,后端只应该使用前端传递的 remark 字段,receivedAmount字段不应该使用,但假如用户修改订单备注时,前端不小心传递了receivedAmount字段,并且赋值为 null,这时使用BeanUtils.copyProperties后,OrderDTO 里的receivedAmount字段就也为 null,如果后端不知道前端传递了这个字段并且操作 DB 不够严谨,就会导致订单的已收金额被清空,很恐怖,而且不好排查原因。

3 . 插件推荐

  • 虽然BeanUtils.copyProperties工具提供了便利,但带来的问题也很多,因此很多公司(包含我现在所在的公司)都禁止在项目中使用该工具。

  • 但重复的写对象转换,实在是太繁琐,效率太低了,这里推荐一个 IDEA 的插件GenerateAllSetter,可以一键生成对象的 set 方法,非常方便,如下图所示:

正确地使用 BeanUtils.copyProperties_第4张图片

  • 在需要生成 set 方法的对象上,按快捷键Option+Enter(Windows 是Alt+Enter),会看到下图所示的选项:

正确地使用 BeanUtils.copyProperties_第5张图片

  • 点击后会自动生成所有字段(没有默认值)的赋值语句:

正确地使用 BeanUtils.copyProperties_第6张图片

  • 如果生成赋值语句时想带默认值,可以使用另一个选项:

正确地使用 BeanUtils.copyProperties_第7张图片

  • 效果如下所示:

正确地使用 BeanUtils.copyProperties_第8张图片

4 . 更好的选择

  • 现在 Java 的后端开发,普遍使用的是 Spring 生态,由于 BeanUtils 是 Spring 自带的工具类,亲儿子的出镜率相对较高,但他并不是效率最高的;如果项目中转换比较多,系统并发量比较大,比较关注性能的话,建议使用:MapStruct
  • 这篇文章会详细讲解:MapStruct 的具体使用

你可能感兴趣的:(各大实战问题分析,java)