BeanUtil.copyProperties()进行属性拷贝时如何忽略NULL值——CopyOptions配置详解

一、需求背景

在日常Java开发中,我么通常会使用 BeanUtilscopyProperties() 方法来实现对象属性复制。然而在实际业务场景中,我们经常会遇到这样的需求:

当源对象属性值为NULL时,不希望覆盖目标对象原有的非NULL值。例如:

// 用户更新信息场景
UserDTO userDTO = new UserDTO();
userDTO.setName("张三");
userDTO.setAge(null);  // 年龄字段为null

User targetUser = userService.getUserById(1);
// 原始targetUser.age=25
BeanUtils.copyProperties(userDTO, targetUser); 
// 此时targetUser.age会被覆盖为null,不符合预期

二、CopyOptions核心配置解析

2.1 CopyOptions概述

在Hutool工具包的BeanUtil中,copyProperties()方法提供了更灵活的CopyOptions参数来控制拷贝行为:

public static void copyProperties(Object source, Object target, CopyOptions copyOptions)
CopyOptions

采用建造者模式设计,主要包含以下配置项:

配置项 类型 默认值 说明
ignoreNullValue boolean false 是否忽略NULL值
ignoreCase boolean false 是否忽略字段名称大小写
ignoreError boolean false 是否忽略拷贝错误
editable Class<?> null 限制目标对象类型
ignoreProperties String[] null 要忽略的属性名数组
override boolean true 是否覆盖已有值
fieldMapping Map<String, String> null 字段名称映射关系

2.2 忽略NULL值的配置方法

要实现忽略NULL值的拷贝,需要创建CopyOptions并设置 ignoreNullValue 为true:

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.CopyOptions;

// 创建配置
CopyOptions copyOptions = CopyOptions.create()
        .setIgnoreNullValue(true);  // 关键配置

// 执行拷贝
BeanUtil.copyProperties(source, target, copyOptions);

三、深度配置示例

3.1 复合配置示例

// 复杂场景配置示例
CopyOptions options = CopyOptions.create()
        .setIgnoreNullValue(true)    // 忽略null值
        .setIgnoreCase(true)         // 忽略字段大小写
        .setIgnoreProperties("password") // 忽略密码字段
        .setOverride(false)          // 不覆盖已有值
        .setFieldMapping(Collections.singletonMap("userName", "name")); // 字段映射

BeanUtil.copyProperties(dto, entity, options);

3.2 与Spring框架的对比

Spring的BeanUtils.copyProperties()默认不支持忽略NULL值,需要自行扩展:

// Spring场景下的NULL值忽略实现
public class CustomBeanUtils extends BeanUtils {
    public static void copyPropertiesIgnoreNull(Object src, Object target) {
        PropertyDescriptor[] srcPds = getPropertyDescriptors(src.getClass());
        for (PropertyDescriptor srcPd : srcPds) {
            if (srcPd.getReadMethod() != null) {
                try {
                    Object value = srcPd.getReadMethod().invoke(src);
                    if (value != null) {  // 只拷贝非NULL值
                        PropertyDescriptor targetPd = 
                            getPropertyDescriptor(target.getClass(), srcPd.getName());
                        if (targetPd != null && targetPd.getWriteMethod() != null) {
                            targetPd.getWriteMethod().invoke(target, value);
                        }
                    }
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }
}

四、特殊场景处理

4.1 嵌套对象拷贝

对于嵌套对象的NULL值处理,需要递归配置:

public static void deepCopyIgnoreNull(Object source, Object target) {
    CopyOptions options = CopyOptions.create().setIgnoreNullValue(true);
    BeanUtil.copyProperties(source, target, options);
    
    // 获取所有嵌套对象字段
    Field[] fields = source.getClass().getDeclaredFields();
    for (Field field : fields) {
        if (field.getType().getName().startsWith("com.yourpackage")) {
            try {
                field.setAccessible(true);
                Object srcChild = field.get(source);
                Object targetChild = field.get(target);
                if (srcChild != null && targetChild != null) {
                    deepCopyIgnoreNull(srcChild, targetChild);
                }
            } catch (IllegalAccessException e) {
                // 异常处理
            }
        }
    }
}

4.2 集合属性拷贝

// 集合属性拷贝示例
CopyOptions options = CopyOptions.create().setIgnoreNullValue(true);

List<SourceDTO> sourceList = ...;
List<TargetVO> targetList = new ArrayList<>();

for (SourceDTO source : sourceList) {
    TargetVO target = new TargetVO();
    BeanUtil.copyProperties(source, target, options);
    targetList.add(target);
}

通过合理配置CopyOptions,可以极大提升对象属性拷贝的灵活性和安全性,满足各种业务场景的需求。