beanutils.copyproperties 拷贝失败主因是字段名不匹配、类型不兼容或 getter/setter 不规范;spring 版轻量快但无类型转换,apache 版功能全但线程不安全;均不支持 null 跳过、集合深拷贝及嵌套自动处理。

BeanUtils.copyProperties 为什么经常拷贝失败
直接调用 BeanUtils.copyProperties(source, target) 却发现 target 的属性没变,大概率是因为字段类型不匹配或 getter/setter 不规范。Apache Commons BeanUtils 默认只拷贝「同名且类型兼容」的属性,比如 String → String 没问题,但 LocalDateTime → String 或 int → Integer(反向)会被跳过;另外,如果 getter 返回 Optional<t></t> 或 setter 参数是泛型接口,也会静默忽略。
- 确保 source 和 target 的字段名完全一致(大小写敏感)
- 检查 getter/setter 是否存在且符合 JavaBean 规范(如
getCreateTime()对应createTime字段) - 避免在字段上使用 Lombok 的
@Accessors(chain = true),它会让 setter 返回this,破坏标准 setter 签名 - 若需转换类型(如
Date↔LocalDateTime),必须配合自定义ConvertUtils.register(),否则直接丢弃
Spring BeanUtils.copyProperties 和 Apache 版本的区别在哪
Spring 的 BeanUtils.copyProperties 更轻量、更常用,但它不支持类型转换注册,也不处理嵌套对象(如 user.getAddress().getCity())。Apache 版本功能更全,但依赖重、线程不安全(需注意 ConvertUtils 是静态全局注册,多线程下可能冲突)。
- 优先选 Spring 版:项目已引入 Spring,且只需简单同类型拷贝
- 选 Apache 版:需要批量注册类型转换器(如把所有
String时间格式转为LocalDateTime) - 两者都不处理 null 值覆盖 —— 如果 source 字段为
null,target 对应字段也会被设为null,无法跳过 - 性能上,Spring 版用反射缓存 + 方法句柄优化,通常比 Apache 快 20%~30%
如何跳过 null 值、指定字段或深层嵌套拷贝
原生 BeanUtils.copyProperties 不支持条件过滤,必须自己封装或换方案。常见做法是先用反射获取非 null 属性名,再逐个 copy;或者改用 DozerBeanMapper / MapStruct,但它们引入新学习成本和构建阶段依赖。
- 手动跳过 null:用
PropertyUtils.getPropertyDescriptors(source.getClass())遍历,对每个属性调用PropertyUtils.getSimpleProperty(source, name)判空后再 set - 排除字段:Spring 5.3+ 提供
BeanUtils.copyProperties(source, target, "id", "version"),第三个参数是忽略字段数组 - 嵌套拷贝(如
User→UserDTO同时拷贝address.city):必须手写或用 MapStruct 的@Mapping(target = "city", source = "address.city") - 注意:
BeanUtils永远不会自动递归拷贝嵌套对象,source.getAddress()是 null,target 的 address 字段也还是 null,不会新建实例
为什么 copy 后 target 的 List 字段为空或报错
BeanUtils 把 List 当作普通属性处理,只做引用拷贝(shallow copy),不会遍历元素做深拷贝。如果 source 的 list 是 ArrayList,target 的 list 字段类型是 LinkedList,还会因类型不匹配而跳过。
- 不要依赖
BeanUtils处理集合字段 —— 它不识别泛型,也不调用add()方法 - 正确做法:先 copy 基础字段,再单独处理集合,例如
target.setOrders(source.getOrders().stream().map(Order::clone).collect(Collectors.toList())) - 若用 Lombok,确保
@Data类实现了Cloneable并重写了clone(),否则Order::clone会抛CloneNotSupportedException - 更稳妥的是用 Jackson:先
ObjectMapper.convertValue(source, Target.class),它会按类型推导并深拷贝集合
copyProperties 解决所有问题。null 处理、集合、时间类型、嵌套对象,每个都是显式要写的逻辑。越想“快速”,越容易在上线后发现数据丢了。Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











