Spring的BeanUtils.copyProperties方法

本文详细介绍了Spring框架中BeanUtils的copyProperties方法的工作原理及使用注意事项。该方法用于实现两个对象间属性的复制,要求目标对象与源对象的属性名称和类型完全匹配。
  • 用途

    BeanUtils的copyProperties方法用来做属性copy。
    使用时的注意点:

    1. target和source的属性名类型和名称必须相同
    2. 名称大小写敏感
    3. 如果名称大小写不一致,或者类型不一致,则跳过,不做属性复制

  • 原理

    copyProperties使用了jdk自带的自省机制自省 简单来说就是jdk在 反射 上又做了一层包装,针对于Bean对象的属性读写。

    大概逻辑如下:

    1. 遍历target对象的属性
    2. 获取target对象属性的write方法
    3. 根据target对象的属性名,获取source对象的属性
    4. 如果有同样名字的属性,调用source对象属性的read方法得到值
    5. 调用target对象属性的write方法,给属性赋值

    主要源码如下:

    public abstract class BeanUtils {
    
        //省略其余代码
    
        public static void copyProperties(Object source, Object target) throws BeansException {
            copyProperties(source, target, null, (String[]) null);
        }
    
        public static void copyProperties(Object source, Object target, Class<?> editable) throws BeansException {
            copyProperties(source, target, editable, (String[]) null);
        }
    
        public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {
            copyProperties(source, target, null, ignoreProperties);
        }
    
        private static void copyProperties(Object source, Object target,
                @Nullable Class<?> editable, 
                @Nullable String... ignoreProperties 
                ) throws BeansException {
    
            Assert.notNull(source, "Source must not be null");
            Assert.notNull(target, "Target must not be null");
    
            Class<?> actualEditable = target.getClass();
            if (editable != null) {
                if (!editable.isInstance(target)) {
                    throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
                            "] not assignable to Editable class [" + editable.getName() + "]");
                }
                actualEditable = editable;
            }
            //获取target的属性列表,这里会做class的缓存
            PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
            //忽略的属性列表
            List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
    
            //循环target的属性列表
            for (PropertyDescriptor targetPd : targetPds) {
                //获取写方法
                Method writeMethod = targetPd.getWriteMethod();
                if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
                    //根据target的属性名,获取source对应的属性
                    PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
                    if (sourcePd != null) {
                        //获取source属性的读方法
                        Method readMethod = sourcePd.getReadMethod();
                        //校验写方法的参数类型和读方法的返回类型是否一致,如果不一致则跳过
                        if (readMethod != null &&
                                ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                            try {
                                //如果读方法不是public的,则设置accessible=true
                                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                    readMethod.setAccessible(true);
                                }
                                Object value = readMethod.invoke(source);
                                //如果写方法不是public的,则设置accessible=true
                                if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                    writeMethod.setAccessible(true);
                                }
                                //target.setXXX(source.getXXX())
                                writeMethod.invoke(target, value);
                            }
                            catch (Throwable ex) {
                                throw new FatalBeanException(
                                        "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                            }
                        }
                    }
                }
            }
        }
    
        public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
            CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
            return cr.getPropertyDescriptors();
        }
    }
在Java开发中,特别是在Spring框架中,`BeanUtils.copyProperties` 方法被广泛用于复制对象之间的属性值。该方法通过反射机制实现属性的自动拷贝,从而减少手动编写 `getter` 和 `setter` 的冗余代码。 ### 使用示例 以下是一个简单的使用示例: ```java import org.springframework.beans.BeanUtils; public class Example { public static void main(String[] args) { SourceObject source = new SourceObject(); source.setName("Test"); source.setAge(25); TargetObject target = new TargetObject(); BeanUtils.copyProperties(target, source); // 注意参数顺序:目标在前 System.out.println(target.getName()); // 输出: Test System.out.println(target.getAge()); // 输出: 25 } } class SourceObject { private String name; private int age; // getters and setters public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } class TargetObject { private String name; private int age; // getters and setters public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } ``` ### 注意事项 - **属性名称和类型必须一致** `BeanUtils.copyProperties` 在进行属性拷贝时要求源对象和目标对象的属性名完全一致(包括大小写),并且属性类型也必须匹配。如果名称或类型不一致,则不会执行拷贝操作[^1]。 - **泛型仅在编译期有效** 泛型信息在运行时会被擦除,因此不能依赖泛型来做运行期的类型限制。例如,`ArrayList<Integer>` 在运行时实际上是一个普通的 `ArrayList`,可以通过反射添加非整数类型的元素[^4]。 - **可选参数控制拷贝行为** Spring 提供了更灵活的 `copyProperties` 方法签名,允许传入可选的类类型和忽略的属性名列表,以控制哪些属性应该被跳过: ```java private static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) ``` 此方法允许开发者指定特定类的属性进行拷贝,并忽略某些不需要复制的字段[^3]。 - **性能考量** 虽然 `BeanUtils.copyProperties` 提供了便利性,但由于其底层依赖于反射机制,因此在频繁调用时可能会影响性能。对于高性能敏感场景,建议使用其他方式如手动赋值或使用 MapStruct 等工具进行优化。 - **异常处理** 该方法可能会抛出 `BeansException` 异常,因此在使用过程中需要做好异常捕获和处理工作。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值