惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
爱范儿
爱范儿
博客园_首页
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
IT之家
IT之家
WordPress大学
WordPress大学
N
Netflix TechBlog - Medium
Microsoft Azure Blog
Microsoft Azure Blog
The GitHub Blog
The GitHub Blog
C
Check Point Blog
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - Franky
G
Google Developers Blog
V
V2EX
雷峰网
雷峰网
美团技术团队
博客园 - 【当耐特】
人人都是产品经理
人人都是产品经理
有赞技术团队
有赞技术团队
MongoDB | Blog
MongoDB | Blog
V
Visual Studio Blog
J
Java Code Geeks

博客园 - 五岳

JDK中使用的设计模式 Java的List.sort()排序方法源码理解 Cloud Agent 开发笔记(4):Skill 与 MCP 集成、项目后记 Cloud Agent 开发笔记(3):Web 交互与数据持久化 Cloud Agent 开发笔记(2):Agent 引擎与 Tool 体系 Cloud Agent 开发笔记(1):V1从跑通到放弃 单点登录系统思维导图与资料收集 阿里云DTS按业务场景批量迁移RDS MySQL表实战(下):迁移管理平台设计与实现 阿里云DTS按业务场景批量迁移RDS MySQL表实战(上):技术选型和API对接 分库分表数据源ShardingSphereDataSource的Connection元数据误用问题分析 Web层接口通用鉴权注解实践(基于JDK8) Feign框架中一处编码不合理导致的异常 解决Dify的Ollama插件添加模型时保存成功但模型为空的问题 mybatis-config的mapUnderscoreToCamelCase配置生效方式和基本原理 封装CompletionService的并发任务分发器(优化版) 深入理解Mybatis分库分表执行原理 写代码被大语言模型坑之使用LocalDateTime比较两个时间差了几天 系统设计:消灭慢接口 使用LinkedList实现队列和栈 Collectors.toMap的暗坑与避免方式 如丝般顺滑:DDD再实践之类目树管理 阿里云数仓Dataworks数据导出到文件step by step DDD实践反思 LeetCode组合总和I~IV和背包问题小结 Akka学习笔记
深入研究使用DozerMapper复制List<Ojbect>前后元素类型...
五岳 · 2025-07-04 · via 博客园 - 五岳

背景

某项目某个功能点是接受前端传参,将其存入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:
image

问题定位

进行debug时,发现在对456调用primitiveConverter.convert()时,此时是知道该元素类型是Integer,调用的返回值却成了字符串:
image

深入一层,可以看到convert()做了两件事:先确认使用哪个Converter,然后由这个Converter进行实际的转换。这里暗藏了问题:取Converter时,没有用原始数据的实际类型信息,而是取的是Object(这里为什么是Object,接下来会继续深入探讨):
image

Object类型取不到对应的Converter,就由以下的分支判断,最后还是取不到,就是使用了StringConstructorConverter:
image
StringConstructorConverter的内部调用了StringConverter,实际上做的只不过是调用了toString(),因此456变成了"456"

寻根究底

Ojbect类型是从哪里取的?

取Converter时,destFieldType=java.lang.Object,是怎么来的?直觉上,我认为是从List