深入研究使用DozerMapper复制List<Ojbect>前后元素类型不一致的问题,这个传参的核心数据是
背景
某项目某个功能点是接受前端传参,将其存入MongoDB。这个传参的核心数据是一个二维数组List<List<Object>>
,可以放字符串、整型,也可以放null。
在测试时发现,前端明明传的是整数,查出来却变成了字符串,比如1234
变成了"1234"
。经过排查发现,问题出在公司内部使用的一个Bean复制工具类,这个工具类简单封装了DozerMapper,主要功能是将一个Bean复制成一个新的Bean,并且允许这两个Bean的Class不同,从而完成各种类型转换,如:VO <-> Model、Model <-> DO、DO <-> DTO等。
为了快速修复问题从而不影响项目进度,我手写了前端传参和MongoDB的Entity类的转换逻辑,规避了这个问题。这个工具类在公司内部的代码中大量使用,问题的根因是什么?为了搞明白,我写了一个简单的demo,通过debug这部分代码来一探究竟。
关于DozerMapper
DozerMapper有一些高级用法和对应的传参,但是日常中仅仅用到DozerBeanMapperBuilder.buildDefault()来处理。
DozerMapper的官方github,在mvnrepository上可以看到它的最新版本是7.0.0。
公司的工具类用的是6.5.2,也就是6.x的最后一个版本。经验证:
- 7.0.0和6.5.2都有这个bug
- 6.5.2可以运行在JDK8,7.0.0必须运行在JDK11及以上
本文基于JDK8+DozerMapper6.5.2分析。
问题简化和复现
将实际的传参简化如下。该类必须有无参数构造器,否则DozerMapper创建Bean时会报错。
public class ListObjectWrapper {
private List<Object> list;
public ListObjectWrapper() {
}
public ListObjectWrapper(List<Object> list) {
this.list = list;
}
public List<Object> getList() {
return list;
}
public void setList(List<Object> list) {
this.list = list;
}
}
对应的测试代码:
public class Test {
public static void main(String[] args) {
Mapper mapper = DozerBeanMapperBuilder.buildDefault();
List<Object> list = new ArrayList<Object>();
list.add("123");
list.add(456);
list.add(null);
list.add(new Date());
ListObjectWrapper wrapper1 = new ListObjectWrapper(list);
ListObjectWrapper wrapper2 = mapper.map(wrapper1, ListObjectWrapper.class);
for (Object value : wrapper2.getList()) {
if(value == null) {
System.out.println(value);
continue;
}
System.out.println("type:" + value.getClass() + ", value=" + value);
}
}
}
可见,wrapper2的list里的元素全部变成了String:
问题定位
进行debug时,发现在对456
调用primitiveConverter.convert()
时,此时是知道该元素类型是Integer,调用的返回值却成了字符串:
深入一层,可以看到convert()做了两件事:先确认使用哪个Converter,然后由这个Converter进行实际的转换。这里暗藏了问题:取Converter时,没有用原始数据的实际类型信息,而是取的是Object(这里为什么是Object,接下来会继续深入探讨):
Object类型取不到对应的Converter,就由以下的分支判断,最后还是取不到,就是使用了StringConstructorConverter:
StringConstructorConverter的内部调用了StringConverter,实际上做的只不过是调用了toString(),因此456
变成了"456"
。
寻根究底
Ojbect类型是从哪里取的?
取Converter时,destFieldType=java.lang.Object,是怎么来的?直觉上,我认为是从List
作者:五岳
出处:http://www.cnblogs.com/wuyuegb2312
对于标题未标注为“转载”的文章均为原创,其版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
用户点评