有些业务场景下需要将 Java Bean 转成 Map 再使用。
以为很简单场景,但是坑很多。
二、那些坑 2.0 测试对象父类
2.1 JSON 反序列化了类型丢失 2.1.1 问题复现将 Java Bean 转 Map 最常见的手段就是使用 JSON 框架,如 fastjson 、 gson、jackson 等。 但使用 JSON 将 Java Bean 转 Map 会导致部分数据类型丢失。 如使用 fastjson ,当属性为 Long 类型但数字小于 Integer 最大值时,反序列成 Map 之后,将变为 Integer 类型。
maven 依赖:
示例代码:
结果打印:
{"parent":3,"ADouble":3.4,"ALong":2,"AInteger":1,"ADate":1657299916477}
调试截图:
通过 Java Visualizer 插件进行可视化查看:
2.2.2 问题描述存在两个问题 (1) 通过 fastjson 将 Java Bean 转为 Map ,类型会发生转变。 如 Long 变成 Integer ,Date 变成 Long, Double 变成 Decimal 类型等。 (2)在某些场景下,Map 的 key 并非和属性名完全对应,像是通过 get set 方法“推断”出来的属性名。
2.2 BeanMap 转换属性名错误 2.2.1 commons-beanutils 的 BeanMapmaven 版本:
代码示例:
调试截图:
存在和 cglib 一样的问题,虽然类型没问题但是属性名还是不对。
原因分析:
关键代码:
调试一下就会发现,问题出在 BeanInfo 里面 PropertyDescriptor 的 name 不正确。
经过分析会发现 java.beans.Introspector#getTargetPropertyInfo 方法是字段解析的关键
对于无参的以 get 开头的方法名从 index =3 处截取,如 getALong 截取后为 ALong, 如 getADouble 截取后为 ADouble。
然后去构造 PropertyDescriptor:
底层使用 java.beans.Introspector#decapitalize 进行解析:
从代码中我们可以看出 (1) 当 name 的长度 > 1,且第一个字符和第二个字符都大写时,直接返回参数作为PropertyDescriptor name。 (2) 否则将 name 转为首字母小写
这种处理本意是为了不让属性为类似 URL 这种缩略词转为 uRL ,结果“误伤”了我们这种场景。
2.2.2 使用 cglib 的 BeanMapcglib 依赖
代码示例:
结果展示:
我们发现类型对了,但是属性名依然不对。
关键代码: net.sf.cglib.core.ReflectUtils#getBeanGetters 底层也会用到 java.beans.Introspector#decapitalize 所以属性名存在一样的问题就不足为奇了。
三、解决办法 3.1 解决方案解决方案有很多,本文提供一个基于 dubbo的解决方案。
maven 依赖:
示例代码:
调试效果:
Java Visualizer 效果:
3.2 原理解析大家可以下载源码来简单研究下。 github.com/apache/dubb…
核心代码: org.apache.dubbo.common.utils.PojoUtils#generalize(java.lang.Object)
关键代码:
关键截图
org.apache.dubbo.common.utils.ReflectUtils#getPropertyNameFromBeanReadMethod
因此, getALong 方法对应的属性名被解析为 aLong。
同时,这么处理也会存在问题。如当属性名叫 URL 时,转为 Map 后 key 就会被解析成 uRL。
从这里看出,当属性名比较特殊时也很容易出问题,但 dubbo 这个工具类更符合我们的预期。 更多细节,大家可以根据 DEMO 自行调试学习。
如果想严格和属性保持一致,可以使用反射获取属性名和属性值,加缓存机制提升解析的效率。
四、总结Java Bean 转 Map 的坑很多,最常见的就是类型丢失和属性名解析错误的问题。 大家在使用 JSON 框架和 Java Bean 转 Map 的框架时要特别小心。 平时使用某些框架时,多写一些 DEMO 进行验证,多读源码,多调试,少趟坑。
到此这篇关于Java Bean转Map的那些踩坑的文章就介绍到这了,更多相关Java Bean转Map的坑内容请搜索七叶笔记以前的文章或继续浏览下面的相关文章希望大家以后多多支持七叶笔记!