一、概述

BeanUtil.copyProperties(source, target) 是 Hutool 工具库中用于 对象属性拷贝 的核心方法之一,功能类似于 Spring 的 BeanUtils.copyProperties,但更灵活、性能更好,并支持更多转换场景。

注意:Hutool 的 copyProperties 默认是 浅拷贝,且 只拷贝同名字段。

二、基本用法

  1. 引入依赖(Maven)
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.22</version> <!-- 推荐使用最新稳定版 -->
</dependency>
  1. 简单示例
public class UserDTO {
    private String name;
    private Integer age;
    // getter/setter
}

public class UserVO {
    private String name;
    private String age; // 注意:类型不同!
    // getter/setter
}

// 使用
UserDTO dto = new UserDTO();
dto.setName("张三");
dto.setAge(25);

UserVO vo = new UserVO();
BeanUtil.copyProperties(dto, vo);

三、核心特性

特性 说明

  • 同名字段自动映射 源对象和目标对象字段名相同即尝试拷贝
  • 类型自动转换 支持常见类型互转(如 Integer ↔ String, Date ↔ String 等)
  • 忽略 null 值(可选) 可通过 BeanCopyOptions 控制是否跳过 null
  • 忽略大小写(可选) 支持字段名忽略大小写匹配
  • 自定义转换器 可注册 Converter 处理特殊类型
  • 支持泛型 对 List、Map 等容器内元素也可转换(需配合配置)

四、高级用法:BeanCopyOptions

通过 BeanCopyOptions 精细控制拷贝行为:


BeanCopyOptions options = BeanCopyOptions.create()
    .setIgnoreNullValue(true)           // 忽略 source 中为 null 的字段
    .setIgnoreCase(true)                // 字段名忽略大小写(如 userName ↔ username)
    .setTransientSupport(true)          // 是否拷贝 transient 字段(默认 false)
    .setFieldMapping(Map.of("oldName", "newName")); // 自定义字段映射(非同名)

BeanUtil.copyProperties(source, target, options);

注意:BeanCopyOptions 是线程安全的,可复用。

五、重点问题:字段名相同但类型不同(笔者实际遇到的问题)

这是实际开发中最常见的痛点。Hutool 的处理逻辑如下:

  1. 默认行为:尝试自动转换
    Hutool 内置了丰富的 Converter,能自动处理以下常见转换:
    源类型 → 目标类型 是否支持
    Integer → String
    String → Integer (如 “123” → 123)
    Date → String (格式:yyyy-MM-dd HH:mm:ss)
    String → Date (支持多种格式自动识别)
    Boolean ↔ String (“true”/“false”)
    Long ↔ String
    BigDecimal ↔ String
    成功示例:
source.setAge(25); // Integer
// copy to target.age (String)
// 结果:target.getAge() == "25"
  1. 转换失败时的行为
    如果无法转换(如 “abc” → Integer),抛出 ConvertException;
    转换过程不会静默失败,确保数据一致性。

六、解决“同名不同型”问题的策略

策略 1:利用 Hutool 内置转换能力(推荐)
只要数据格式合法,Hutool 能自动完成大部分基础类型互转。
适用场景:String ↔ 数值/日期/布尔 等标准类型。
策略 2:注册自定义 Converter
当内置转换不满足需求时,可自定义转换器:

// 示例:将 String "男"/"女" 转为 Boolean(true=男)
ConverterRegistry.getInstance().putCustom(String.class, Boolean.class, (value, targetType) -> {
    if ("男".equals(value)) return true;
    if ("女".equals(value)) return false;
    throw new IllegalArgumentException("性别值非法: " + value);
});

// 使用
BeanUtil.copyProperties(userDTO, userVO); // 自动应用自定义转换

注意:ConverterRegistry 是全局单例,注册一次全局生效。

策略 3:使用字段映射(Field Mapping)绕过冲突
如果某字段类型差异太大(如 List → String),可重命名目标字段,并通过 BeanCopyOptions 映射:

public class Target {
    private String tagListStr; // 原本想叫 tags,但类型不同
}

BeanCopyOptions options = BeanCopyOptions.create()
    .setFieldMapping(Map.of("tags", "tagListStr"));

BeanUtil.copyProperties(source, target, options);
然后在 setter 中手动处理逻辑。

策略 4:分步拷贝 + 手动设置 (笔者个人及其不推荐)
对特殊字段单独处理:

// 先拷贝通用字段
BeanUtil.copyProperties(source, target);

// 再手动处理冲突字段
target.setCreateTimeStr(DateUtil.format(source.getCreateTime(), "yyyy-MM-dd"));
target.setStatusDesc(getStatusDesc(source.getStatus()));

优点:清晰可控;❌ 缺点:代码量增加。

策略 5:使用中间 DTO 层
设计一个与 source 结构一致的中间对象,先拷贝到中间对象,再手动转为目标对象。
适用于复杂转换场景。

七、重要注意事项(避坑指南)

问题说明建议
不拷贝 static/final 字段Hutool 默认跳过正常行为,无需担心
不支持嵌套对象深度拷贝默认只拷贝第一层引用如需深拷贝,用 BeanUtil.toBean 或序列化方式
getter/setter 必须存在依赖 JavaBean 规范确保字段有 public getter/setter
List ↔ List 可能失败泛型擦除影响集合转换避免跨类型集合直接拷贝
日期格式依赖系统默认String→Date 使用内置格式集如需指定格式,建议手动转换
性能 vs 功能权衡反射+转换有一定开销高频场景可考虑 MapStruct 等编译期方案

八、替代方案对比

方案优点缺点适用场景
Hutool BeanUtil简单、灵活、内置转换强运行时反射,性能一般快速开发、中小项目
Spring BeanUtils无额外依赖不支持类型转换Spring 项目简单拷贝
MapStruct编译期生成代码,性能极高需要注解、学习成本高性能、大型项目
Dozer支持复杂映射重量级、已停止维护老项目迁移
ModelMapper配置灵活性能较差动态映射需求

九、总结

BeanUtil.copyProperties 是 强大且易用 的对象拷贝工具;
同名不同型 问题可通过 自动转换 + 自定义 Converter 解决;
遇到转换异常时,不要忽略,应检查数据合法性或注册转换器;
复杂场景建议 组合使用:自动拷贝 + 手动补全;
对于高频、高性能要求场景,考虑 MapStruct 等编译期方案。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐