首页>>后端>>Spring->Spring的BeanUtils.copyProperties()避坑指南

Spring的BeanUtils.copyProperties()避坑指南

时间:2023-11-29 本站 点击:1

我们项目中经常使用Spring的BeanUtils.copyProperties()方法,进行对象之间属性的拷贝,来替换繁琐的get()、set()方法。但是稍加不注意,使用此方法就会出现意向不到的问题。今天就聊聊常见的坑,并从源码角度分析问题出现的原因。

常见的「坑」

1. 不声明属性的get、set方法,属性将copy失败

实际项目中通常使用Lombok插件的@Data注解以省略get、set方法

publicclassSourceBean{privateintid;privateStringname;publicSourceBean(){}publicSourceBean(intid,Stringname){this.id=id;this.name=name;}}

publicclassTargetBean{privateintid;privateStringname;publicTargetBean(){}publicTargetBean(intid,Stringname){this.id=id;this.name=name;}}

从调试情况看,target属性并未拷贝成功。

2. copy为浅拷贝(拷贝对象的引用)
@DatapublicclassSourceBean{privateintid;privateStringname;privateMap<String,String>content;}

@DatapublicclassTargetBean{privateintid;privateStringname;privateMap<String,String>content;}

publicclassMain{publicstaticvoidmain(String[]args){SourceBeansourceBean=newSourceBean();Map<String,String>sourceContent=newHashMap<>();sourceContent.put("name","Bob");sourceBean.setContent(sourceContent);TargetBeantargetBean=newTargetBean();BeanUtils.copyProperties(sourceBean,targetBean);//targetBean内容System.out.println(targetBean.getContent().get("name"));//只修改sourceBean内容sourceBean.getContent().put("name","Peter");//targetBean内容同时被修改System.out.println(targetBean.getContent().get("name"));}}//控制台输出结果BobPeter

3. Spring不同版本对属性泛型处理方式不同

Spring5.3之后,匹配源对象和目标对象中的属性时遵循泛型类型信息,意思是copy属性时,会判断属性的泛型是否一致,如不一致,直接忽略属性的拷贝。

@DatapublicclassSourceBean{privateList<Integer>ids;}

@DatapublicclassTargetBean{privateList<String>ids;}

5.3.8版本运行情况:targetBean的ids依旧为null

5.2.10版本运行情况:targetBean的ids拷贝了sourceBean的ids

4. TargetBean拷贝的成员属性实际类型可能跟声明不一致

属性有泛型时会发生,可能会导致运行出错。 还是上面的Bean,执行以下代码,运行时将会抛出异常

publicstaticvoidmain(String[]args){SourceBeansourceBean=newSourceBean();sourceBean.setIds(Arrays.asList(1,2,3));TargetBeantargetBean=newTargetBean();BeanUtils.copyProperties(sourceBean,targetBean);StringfirstId=targetBean.getIds().get(0);}

BeanUtils.copyProperties()源码解析

上面所说的常见的「坑」,翻看Spring的BeanUtils.copyProperties()方法源码,就很容易发现问题出现的原因。

源码核心部分(5.3.8版本):

privatestaticvoidcopyProperties(Objectsource,Objecttarget,@NullableClass<?>editable,@NullableString...ignoreProperties)throwsBeansException{//...其他Class<?>actualEditable=target.getClass();//...其他//获取目标类的所有属性描述(PropertyDescriptor,主要包括属性名称和其相关的读写方法即set、get方法)PropertyDescriptor[]targetPds=getPropertyDescriptors(actualEditable);//...其他//遍历所有属性,为每个属性赋值for(PropertyDescriptortargetPd:targetPds){//获取属性的set方法MethodwriteMethod=targetPd.getWriteMethod();if(writeMethod!=null&&(ignoreList==null||!ignoreList.contains(targetPd.getName()))){//获取targetBean属性对应sourceBean的属性描述PropertyDescriptorsourcePd=getPropertyDescriptor(source.getClass(),targetPd.getName());if(sourcePd!=null){//sourceBean属性的读方法,即get方法MethodreadMethod=sourcePd.getReadMethod();if(readMethod!=null){ResolvableTypesourceResolvableType=ResolvableType.forMethodReturnType(readMethod);ResolvableTypetargetResolvableType=ResolvableType.forMethodParameter(writeMethod,0);//判断属性类型是否一致,包括泛型是否一致booleanisAssignable=(sourceResolvableType.hasUnresolvableGenerics()||targetResolvableType.hasUnresolvableGenerics()?ClassUtils.isAssignable(writeMethod.getParameterTypes()[0],readMethod.getReturnType()):targetResolvableType.isAssignableFrom(sourceResolvableType));if(isAssignable){try{if(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())){readMethod.setAccessible(true);}//通过反射,调用get方法,获取source属性的值Objectvalue=readMethod.invoke(source);if(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())){writeMethod.setAccessible(true);}//通过反射,调用set方法,将source属性的值赋值给target的属性writeMethod.invoke(target,value);}catch(Throwableex){thrownewFatalBeanException("Couldnotcopyproperty'"+targetPd.getName()+"'fromsourcetotarget",ex);}}}}}}

从源码可以看出:

sourceBean和targetBean的属性的拷贝,是通过反射中的Method完成的,所以如果Bean不声明属性的set和get方法,则不能属性间的copy。

Method的invoke方法,只是把sourceBean的get方法获取的值通过targetBean的set方法设置,所以并不涉及深拷贝,只是拷贝属性的引用。

上面的源码,有一步:属性的泛型是否一致判断。

booleanisAssignable=(sourceResolvableType.hasUnresolvableGenerics()||targetResolvableType.hasUnresolvableGenerics()?ClassUtils.isAssignable(writeMethod.getParameterTypes()[0],readMethod.getReturnType()):targetResolvableType.isAssignableFrom(sourceResolvableType));

而在Spring 5.3.0之前并没有这一步,其判断方式:

publicclassTargetBean{privateintid;privateStringname;publicTargetBean(){}publicTargetBean(intid,Stringname){this.id=id;this.name=name;}}0

在5.3.0之前没有泛型的判断,所以通过反射的方法赋值会出现实际的类型与声明的不一致。

总结

BeanUtils.copyProperties()更适合简单Bean之间拷贝,如果Bean属性复杂,很容易因为浅拷贝导致一系列的问题。而且copyProperties方法实现过程并不简单,相对于直接用get和set方法赋值,其性能开销更大。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/Spring/290.html