复写springframework下的BeanUtils.copyProperties方法

编码时,实体类参数名转换常导致重复工作。为解决此问题,参考BeanUtils.copyProperties方法,结合注解重写一套方法,还新建注解、编写测试示例类和对象复制方法。本机测试耗时不到1ms,给出git地址,欢迎交流指导。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

场景: 编码过程中经常遇到,实体类中参数名转换其他参数名,以做后续业务逻辑处理。 同一个功能,可能不同的人接手后,不知道之前转换的方法在哪里,又重新写了一个方法,或者不同的实体,需要转换成同一个实体,这样就造成重复工作浪费。

为解决这个问题,对参数名转换进行封装,提高工作效率, 参考BeanUtils.copyProperties方法,结合注解的使用,重写了一套方法。

 

  • 新建注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RefAnnotation {

    String[] dec() default {};
}
  •  编写两个测试示例类
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class ReflectVo implements Serializable {
    private static final long serialVersionUID = 8827950221777954148L;

    @RefAnnotation(dec = {"aaa","bbb","ccc"})
    private String name;

    @RefAnnotation(dec = {"ddd","eee"})
    private String scott;
}

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class SourceVo implements Serializable {
    private static final long serialVersionUID = -6564361729001137186L;

    private String aaa;
    private String ddd;
}
  •  对象复制方法
public class BeanCopyVo {

    public static void copyProperties(Object source, Object target) throws BeansException {

        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");

        Class<?> actualEditable = target.getClass();
        PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable);

        Field[] fields = actualEditable.getDeclaredFields();

        Map<String, Field> map = new HashMap<>();
        for (Field f : fields) {
            map.put(f.getName(), f);
        }

        for (PropertyDescriptor targetPd : targetPds) {
            Method writeMethod = targetPd.getWriteMethod();

            if (null == writeMethod) {
                continue;
            }

            //获取注解
            String fieldName = targetPd.getName();
            Field field = map.get(fieldName);
            if (null == field) {
                continue;
            }

            if (!field.isAnnotationPresent(RefAnnotation.class)) {
                continue;
            }
            RefAnnotation annotation = field.getAnnotation(RefAnnotation.class);
            String[] dec = annotation.dec();

            if (writeMethod != null) {
                PropertyDescriptor sourcePd = null;
                for (String methodName : dec) {
                    sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), methodName);

                    if (null == sourcePd) {
                        continue;
                    }

                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null && ClassUtils
                            .isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }
                            Object value = readMethod.invoke(source);
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            writeMethod.invoke(target, value);
                        } catch (Throwable ex) {
                            throw new FatalBeanException(
                                    "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                        }
                    }
                }

            }
        }
    }
}
  • 注意 性能影响请结合自己系统测试,本机测试结果耗时不到1ms。

git地址:https://github.com/fengyantao/beancopy

欢迎大家多多交流和指导

### BeanUtils.copyProperties 方法的用法 `BeanUtils.copyProperties()` 是 Spring Framework 提供的一个便捷方法,用于将一个 Java 对象的属性值复制到另一个对象中。该方法通过反射机制自动识别并匹配源对象和目标对象之间的相同属性名,并进行赋值。 ```java public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException ``` - `source`: 源对象,从中读取属性值。 - `target`: 目标对象,将接收来自源对象的属性值。 - `ignoreProperties`: 可选参数,指定在复制过程中忽略的属性名称数组。 例如,假设有两个类 `User` 和 `UserDTO`,它们具有相同的字段名如 `name`、`age` 等,则可以使用如下方式复制属性: ```java User user = new User("Alice", 30); UserDTO userDTO = new UserDTO(); BeanUtils.copyProperties(userDTO, user); // 将user中的属性复制到userDTO中 ``` 需要注意的是,该方法是单向复制,即从 `source` 到 `target`,并且不会自动处理嵌套或复杂类型的属性[^1]。 ### 从 Spring 5.3.x 开始的变化 自 Spring Framework 5.3 起,`BeanUtils.copyProperties()` 方法引入了对泛型类型信息的支持。这是通过内部使用的 `ResolvableType` 类来实现的,它能够更准确地解析和匹配带有泛型参数的属性类型。 这意味着当复制包含泛型集合(如 `List<String>`)或其他复杂泛型结构的属性时,该方法现在能更好地保持类型一致性,从而减少运行时错误的可能性[^2]。 ### 注意事项 1. **可访问性**:确保源对象的 getter 方法和目标对象的 setter 方法都是公开的(public),否则可能会抛出异常或者无法正确复制某些属性。 2. **性能考量**:由于此方法依赖于反射机制,因此其性能通常不如直接调用 getter/setter 方法。对于高性能要求的应用场景,建议谨慎使用或考虑其他替代方案[^4]。 3. **忽略特定属性**:可以通过传递 `ignoreProperties` 参数来排除不需要复制的属性。这对于避免覆盖只读字段或敏感数据非常有用。 4. **类型转换**:如果源属性与目标属性的数据类型不一致,Spring 会尝试进行自动转换。然而,并非所有类型都能被成功转换;在这种情况下,应该手动处理以防止潜在的问题[^4]。 5. **空值处理**:默认情况下,如果源属性为 null,则目标属性也会被设置为 null。根据业务需求,有时可能需要保留原有值而不是用 null 替换。此时需自定义逻辑来控制行为。 6. **不可变对象**:若目标对象中有任何不可变属性(例如 final 字段),则这些属性不能被修改,这会导致调用失败并抛出异常。 7. **安全性**:不要盲目信任输入数据,在安全敏感环境中应验证输入对象的内容,以防注入攻击等安全风险。 8. **版本兼容性**:考虑到不同版本间存在差异,尤其是关于泛型支持方面,请确认所使用的 Spring 版本是否满足项目需求。 9. **异常处理**:该方法声明抛出了 `BeansException`,所以在实际应用中应当妥善捕获并处理此类异常,以免程序因意外情况而中断。 10. **深度拷贝 vs 浅拷贝**:`BeanUtils.copyProperties()` 实现的是浅拷贝。也就是说,如果属性是指针类型(比如另一个对象实例),那么只会复制引用地址而非创建新的副本。要执行深拷贝,则必须自行实现相关逻辑或者借助第三方库如 Dozer、ModelMapper 等。 11. **日志记录**:在调试阶段开启详细的日志输出可以帮助追踪哪些属性被成功复制以及是否存在未能匹配的情况,这对排查问题很有帮助。 12. **单元测试**:为了保证功能正常工作,编写针对各种边缘情况(如空对象、非法参数等)的单元测试是非常重要的一步。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DazedMen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值