bigywater.tech
Spring · 185

BeanUtils.copyProperties、MapStruct 与手写 setter:7 个字段的耗时相差几十到上千倍,以及 Integer 变 Long 悄悄丢值

BeanUtils.copyProperties 和 MapStruct 到底差多少?类型不同、名字不同、null 值各会怎样?漏映射能不能在编译期发现?这篇用同一对 7 个字段的对象,在本机逐个测。

51.3 倍
Spring BeanUtils 拷 7 个字段的耗时是 MapStruct 的 51.3 倍(226.8 ns 对 4.4 ns)
1688.2 倍
Commons BeanUtils 是 MapStruct 的 1688.2 倍(7457.4 ns)
悄悄丢值
Integer 拷到 Long 时,Spring BeanUtils 静默跳过,目标的 age 是 null;MapStruct 和 Commons BeanUtils 做了转换,得到 30

拷贝结果

实测 · 本机执行输出
  Spring BeanUtils.copyProperties:name=张三, age=null, score=99, nick=null, active=true, addr=北京, note=null;addr 是同一个对象:true
  Commons BeanUtils.copyProperties:name=张三, age=30, score=99, nick=null, active=true, addr=北京, note=null;addr 是同一个对象:true
  MapStruct(只映射了 nickName → nick):name=张三, age=30, score=99, nick=三儿, active=true, addr=北京, note=null;addr 是同一个对象:true

类型不同、名字不同、null 值的表现。

编译警告

实测 · 本机执行输出
  编译退出码 0
  demo/M.java:3: warning: Unmapped target property: "nick".
  3 warnings

MapStruct 漏映射有警告。

升级为错误

实测 · 本机执行输出
  编译退出码 1
  demo/M.java:3: error: Unmapped target property: "nick".
  1 error
  1 warning

ReportingPolicy.ERROR 让构建失败。

耗时

实测 · 本机执行输出
  第 1 轮(整套基准各跑一遍)
    manual:5.6 ± 0.2 ns/op
    mapstruct:4.4 ± 0.1 ns/op
    springBeanUtils:227.9 ± 8.5 ns/op
    commonsBeanUtils:7428.5 ± 165.6 ns/op
  第 2 轮(整套基准各跑一遍)
    manual:5.5 ± 0.2 ns/op
    mapstruct:4.4 ± 0.2 ns/op
    springBeanUtils:225.7 ± 15.5 ns/op
    commonsBeanUtils:7486.3 ± 248.3 ns/op
  两轮平均:manual 5.6 ns;mapstruct 4.4 ns;springBeanUtils 226.8 ns;commonsBeanUtils 7457.4 ns
  相对 MapStruct 的倍数:springBeanUtils 51.3 倍;commonsBeanUtils 1688.2 倍

手写、MapStruct、Spring、Commons 四者相差几十到上千倍。

分步演示

BeanCopy
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(Spring Framework 7.0.9 的 spring-beans、Commons BeanUtils 1.10.1、MapStruct 1.6.3,JMH 1.37;源对象 Src 与目标对象 Dst 各 7 个字段;JMH 为 avgt,3 次预热加 5 次测量各 1 秒,2 个 fork,整套基准跑了两轮;MapStruct 用 javac 注解处理器生成实现)
    • 没有验证:字段更多或嵌套对象时的耗时;多线程下的表现;Spring BeanUtils 首次调用的缓存预热成本;Commons BeanUtils 为什么这么慢(只记录了数字);Dozer、ModelMapper、Orika 等其它库;MapStruct 与 Lombok、Spring 组件模型(componentModel = spring)一起使用;JDK 版本与 JIT 对结果的影响;深拷贝(三种方式拷出的 addr 都是同一个对象,只验证了浅拷贝)
    京ICP备2026067502号-1京公网安备11010502063487号