1. 概述

在Java开发中,对象间属性复制是常见需求。Spring框架提供的BeanUtils.copyProperties方法因其便捷性被广泛使用,但默认会复制所有匹配属性。实际开发中常遇到两种场景:

  • 源对象和目标对象结构不同
  • 只需复制部分字段(如敏感数据过滤)

当需要精确控制复制字段时,必须定制化复制行为。本文将介绍三种实现方案,并提供实战代码示例。

2. 使用ignoreProperties参数

BeanUtils.copyProperties支持第三个参数ignoreProperties,可指定不复制的字段名(支持可变参数)。以下为基础模型类:

public class SourceBean {
    private String name;
    private String address;
    private int age;

    // 构造方法和getter/setter
}

public class TargetBean {
    private String name;
    private String address;
    private int age;

    // 构造方法和getter/setter
}

⚠️ 关键点:通过指定"address"排除该字段复制:

@Test
public void givenObjects_whenUsingIgnoreProperties_thenCopyProperties() {
    SourceBean sourceBean = new SourceBean("Peter", 30, "LA");
    TargetBean targetBean = new TargetBean();

    // 排除address字段复制
    BeanUtils.copyProperties(sourceBean, targetBean, "address");
    assertEquals(targetBean.getName(), sourceBean.getName());
    assertEquals(targetBean.getAge(), sourceBean.getAge());
    assertNull(targetBean.getAddress()); // address未被复制
}

适用场景:排除少量字段时最简单粗暴,但字段较多时参数列表会冗长。

3. 自定义工具类封装

当需要显式指定要复制的字段时,可封装工具类:通过反射获取所有属性,反向构建排除列表:

public static void copySpecifiedProperties(Object source, Object target, Set<String> props) {
    String[] excludedProperties = Arrays.stream(BeanUtils.getPropertyDescriptors(source.getClass()))
      .map(PropertyDescriptor::getName)
      .filter(name -> !props.contains(name))
      .toArray(String[]::new);
    BeanUtils.copyProperties(source, target, excludedProperties);
}

🔧 使用示例:只复制nameage字段:

@Test
public void givenObjects_whenUsingCustomWrapper_thenCopyProperties() {
    SourceBean sourceBean = new SourceBean("Peter", 30, "LA");
    TargetBean targetBean = new TargetBean();
    
    // 显式指定要复制的字段
    BeanUtilsCopyProperties
      .copySpecifiedProperties(sourceBean, targetBean, 
        new HashSet<>(Arrays.asList("name", "age")));
      
    assertEquals(targetBean.getName(), sourceBean.getName());
    assertEquals(targetBean.getAge(), sourceBean.getAge());
    assertNull(targetBean.getAddress());
}

优势:字段控制更直观,适合需要白名单机制的场景
缺点:需维护额外工具类,反射操作有轻微性能损耗

4. 中间DTO对象方案

通过创建临时DTO对象作为数据中转站,实现字段过滤:

public class TempDTO {
    public String name;  // 只保留需要复制的字段
    public int age;
}

📝 执行流程:源对象 → DTO → 目标对象

@Test
public void givenObjects_whenUsingIntermediateObject_thenCopyProperties() {
    SourceBean sourceBean = new SourceBean("Peter", 30, "LA");
    
    // 第一步:源对象复制到DTO(自动过滤address)
    TempDTO tempDTO = new TempDTO();
    BeanUtils.copyProperties(sourceBean, tempDTO);

    // 第二步:DTO复制到目标对象
    TargetBean targetBean = new TargetBean();
    BeanUtils.copyProperties(tempDTO, targetBean);
    
    assertEquals(targetBean.getName(), sourceBean.getName());
    assertEquals(targetBean.getAge(), sourceBean.getAge());
    assertNull(targetBean.getAddress());
}

核心价值

  • 字段映射逻辑集中在DTO定义中
  • 避免直接操作源对象
  • 适合复杂字段转换(如重命名、类型转换)

⚠️ 性能提示:涉及两次复制操作,性能略低于前两种方案

5. 方案对比总结

方案 适用场景 优点 缺点
ignoreProperties 排除少量字段 实现简单,无额外代码 字段多时参数冗长
自定义工具类 白名单控制字段 字段列表清晰可维护 需反射操作,有性能损耗
中间DTO对象 复杂字段映射/类型转换 解耦复制逻辑,扩展性强 双次复制,性能最低

💡 最佳实践建议

  1. 简单场景直接用ignoreProperties
  2. 需要显式字段控制时封装工具类
  3. 复杂数据转换优先选DTO方案

踩坑提示:BeanUtils.copyProperties基于属性名匹配,不处理类型转换。当源/目标对象字段类型不兼容时会抛出异常,需提前做好类型检查。


原始标题:Copy Specific Fields by Using BeanUtils.copyProperties in Spring | Baeldung