BeanUtils 踩坑知识
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BeanUtils 踩坑知识相关的知识,希望对你有一定的参考价值。
参考技术A 开发中有一步要发送 http 请求,参数为 map ,原本是有一个 bean 的, 于是就需要转换一下,spring springframework 里有 BeanUtils,包含一个广为人知的 copyProperties 方法,于是点开这个类看了并没有转为map的,
虽然写一个转换方法也不算困难,不过由于时间关系,如果有现成的就直接导包用嘛,
然后搜索了一下,发现 org.apache.commons.beanutils.BeanUtils 有一个 populate(bean, map),的方法,就拿过来用了,
后来测试时候发现map是空的,就很懵逼,然后看了一下方法描述
@param properties Map keyed by property name, with the corresponding (String or String[]) value(s) to be set
就是说先要把 bean 的属性设为 map 的 key 才行,有 key 才会有值,而 new 的 map 当然是空的,结果还是空的。(⊙﹏⊙) 想不通开发者这么做的用意。
无奈正打算重写之际,发现它的第一个方法(因为是d开头)describe 参数为 bean ,return map,跑了一下测试打印出来是这样子的
CSNF=null, CDBH=ererger, class=class com.dto.PeopleData
多一个 class 属性,然后 map.remove("class") ,就得到了想要的结果
CSNF=null, CDBH=ererger
下面是得空补上的手动实现方法
正确地使用 BeanUtils.copyProperties
我是 ABin-阿斌:写一生代码,创一世佳话,筑一览芳华。如果小伙伴们觉得不错就一键三连吧~
声明:原作者:微信公众号:JavaGuide
文章目录
- 程序员的日常搬砖,一旦涉及到业务功能开发,相信对象转换的操作,你一定不会陌生,比如项目中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
类型:
而 OrderVO
中的 userId
字段,我定义的是 long
类型:
然后我们运行下下面所示的代码:
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 空格问题
- 假设
OrderVO
的orderNo
字段,是用户自定义的,用户不小心输入了空格,使用BeanUtils.copyProperties
后,空格会带入到OrderDTO
的orderNo
字段,如果不小心,就会把脏数据落到数据库(而我们希望的是去除空格再落库的),造成一系列后续问题:
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
后,会看到字段并没有引用,其实是有用到的,如下图所示:
- 有些小伙伴在看代码时,看到字段没有地方引用,可能就忍不住想删掉,结果就导致真正使用该字段的地方取不到值,产生 bug。
2.4 前端误传字段,直接把数据库覆盖了
- 如果接口定义的比较严谨,理论上是不应该存在这种情况的,不过凡事总有特殊,这里举个接口不严谨导致数据被覆盖的例子。
假如 OrderVO
和 OrderDTO
有如下 2 个字段:
/**
* 已收金额
* 单位:分
*/
private Long receivedAmount;
/**
* 备注
*/
private String remark;
- 正常情况下,后端只应该使用前端传递的 remark 字段,
receivedAmount
字段不应该使用,但假如用户修改订单备注时,前端不小心传递了receivedAmount
字段,并且赋值为 null,这时使用BeanUtils.copyProperties
后,OrderDTO 里的receivedAmount
字段就也为 null,如果后端不知道前端传递了这个字段并且操作 DB 不够严谨,就会导致订单的已收金额被清空,很恐怖,而且不好排查原因。
3 . 插件推荐
-
虽然
BeanUtils.copyProperties
工具提供了便利,但带来的问题也很多,因此很多公司(包含我现在所在的公司)都禁止在项目中使用该工具。 -
但重复的写对象转换,实在是太繁琐,效率太低了,这里推荐一个 IDEA 的插件
GenerateAllSetter
,可以一键生成对象的set
方法,非常方便,如下图所示:
- 在需要生成 set 方法的对象上,按快捷键
Option+Enter
(Windows 是Alt+Enter
),会看到下图所示的选项:
- 点击后会自动生成所有字段(没有默认值)的赋值语句:
- 如果生成赋值语句时想带默认值,可以使用另一个选项:
- 效果如下所示:
4 . 更好的选择
- 现在 Java 的后端开发,普遍使用的是 Spring 生态,由于
BeanUtils
是 Spring 自带的工具类,亲儿子的出镜率相对较高,但他并不是效率最高的;如果项目中转换比较多,系统并发量比较大,比较关注性能的话,建议使用:MapStruct - 这篇文章会详细讲解:MapStruct 的具体使用
以上是关于BeanUtils 踩坑知识的主要内容,如果未能解决你的问题,请参考以下文章
Spring 的 BeanUtils 踩坑记,你是不是遇到过这些问题?
正确地使用 BeanUtils.copyProperties
java学习--基础知识进阶第十三天--反射机制的概述和字节码对象的获取方式反射操作构造方法成员方法成员属性JavaBean的概述&BeanUtils的使用自定义BeanUtils工