接口开发中BeanUtils.copyProperties的使用

在后台开发过程中我们肯定会使用各种各样的bean,我目前遇到2类

其一是dto:返回给调用方的bean

另外是entity:与数据库映射的bean

由此就会遇到bean直接赋值的问题,我们常常使用BeanUtils.copyProperties(a,b)来进行赋值,将a中的属性赋值给b中的属性(浅复制)


在最近的接口开发过程中遇到的场景:

前端传过来的数据包装成一个dto(a),然后new一个对应的entity(b),利用BeanUtils.copyProperties(a,b),将a中的数据传输给b,之后将b存储在数据库当中,这是非常简单的

我们再来看另外一个场景:

通常,a中的属性值会比b中少,因为,我们很多情况下不会把b中的所有数据都暴露给前端,这对于数据安全是十分重要的,也就是说,同样一个bean,a中的某个属性为null并不代表b中的该属性也是null,很有可能是有值的。

如果前端拿到一个dto,并且修改了其中的某个属性值,如果直接使用BeanUtils.copyProperties(a,b)来进行bean的相互赋值,就会造成b中的数据损失,持久层数据异常。

举例:

b{
user:job
password:123
age:29
}

前端拿到该bean,password属性被屏蔽,然后修改了年龄

a{
user:job
password:null
age:22
}

此时,我们需要将修改的年龄同步到数据库,如果直接使用BeanUtils.copyProperties(a,b)的话

b{
user:job
password:null
age:22
}

可以看到,数据库中b的密码已经为空了,这样你可就惨了~


那么,BeanUtils.copyProperties内部是如何实现这样的赋值功能的呢?我们看一下源码(a等同于source,b等同于target):

        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
        List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
        PropertyDescriptor[] var7 = targetPds;
        int var8 = targetPds.length;

        for(int var9 = 0; var9 < var8; ++var9) {
            PropertyDescriptor targetPd = var7[var9];
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
                    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 var15) {
                            throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15);
                        }
                    }
                }
            }
        }

PropertyDescriptor类表示JavaBean类通过存储器导出一个属性。主要方法:
      1.  getReadMethod(),获得用于读取属性值的方法

      2.  getWriteMethod(),获得用于写入属性值的方法

我们可以看到targetPds是一个PropertyDescriptor数组,长度就等于b的属性数目。

先循环,判断a中sourcePd是否为空(注意,不论a中该属性的值是否为空,只要有该属性就不为空)

然后把a中该属性的值赋给value,注意此时并没有判断value是否为空,所以就造成了上面我们所说持久层数据损失的情况了。



### BeanUtils.copyProperties 方法未成功执行的原因 当 `BeanUtils.copyProperties` 方法未能正常工作时,通常有以下几个可能原因: #### 1. 属性名不匹配 如果源对象和目标对象之间的属性名称不同,则不会自动复制这些属性。即使类型相同,只要名字不一样就不会被处理[^2]。 ```java public class Source { private String name; } public class Target { private String fullName; // 名字不同则无法复制 } ``` #### 2. 数据类型不兼容 尽管该方法可以做一定程度上的类型转换,但如果遇到完全不同的数据类型(比如字符串转整数),可能会抛出异常或跳过某些字段。 #### 3. 集合与数组的支持缺失 此功能仅能实现简单的属性值复制,默认情况下并不能很好地处理复杂的结构如列表、集合或是数组等复合型的数据结构[^3]。 #### 4. 反射机制带来的局限性 由于内部依赖于Java反射来访问私有成员变量,在特定场景下可能导致效率低下甚至失败,尤其是对于复杂嵌套的对象而言更为明显[^4]。 --- ### 解决方案 针对上述提到的各种情况,以下是几种可行的改进措施: #### 使用自定义映射逻辑 对于那些不符合默认规则的情况——例如命名差异较大的字段或者是特殊类型的转换需求,可以通过编写额外的方法来进行一对一的手动设置。 ```java // 手工指定如何从source.getName()映射至target.setFullName() if (source != null && target != null){ target.setFullName(source.getName()); } ``` #### 利用第三方库增强能力 考虑到原生API存在的不足之处,推荐考虑引入更强大的工具类库,像MapStruct这样的框架不仅能够提供更好的性能表现,还具备更加灵活丰富的特性集以满足实际开发中的多样化要求。 ```xml <!-- Maven配置 --> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-jdk8</artifactId> <version>${mapstruct.version}</version> </dependency> <!-- 定义接口并让MapStruct生成具体的实现 --> @Mapper(componentModel = "spring") public interface MyMapper { Target sourceToTarget(Source source); } ``` 通过这种方式可以在很大程度上简化代码量的同时提高系统的稳定性和可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值