浅析BeanUtils
在聊聊BeanUtils之前,我们可以先了解一下PO VO BO DTO 。
- PO (persistant object持久对象)与数据库中对应的对象。
- BO (business object业务对象)处理业务逻辑肯定会涉及到多张表的数据,BO可以将多个PO对象整合一起进行业务处理。
- VO (view object表现层对象) 传递至前端的对象,可以将私密的数据隐藏掉。
- DTO (Data Transfer Object数据传输对象)可以简单理解为前端POST传递数据至后端,后端可以用@RequestBody ObjectDTO 对象进行接收处理。
那么在业务比较复杂的情况下,必然会出现各种DTO,BO,VO,PO相互转换的代码。在业务代码中出现大量的GET,SET方法不美观,容易出错而且耗费精力。由此出现了很多的开源工具,例如springframework的BeanUtils,apache的BeanUtils,dozer等。
本文讨论的是springframework包下的BeanUtils。
import org.springframework.beans.BeanUtils;
BeanUtils.copyProperties(Object source, Object target);
就是把source对象的属性赋值给target对象,但是要注意的是BeanUtils并不会对null进行处理,而是会将其作为属性值直接赋值给target。
例如:
ObjectDTO
@Data
@AllArgsConstructor
public class ObjectDTO {
String name;
int age;
OtherProperty otherProperty;
}
ObjectBO
@Data
@AllArgsConstructor
public class ObjectBO {
String name;
int age;
OtherProperty otherProperty;
double value;
}
#### OtherProperty
@Data
@AllArgsConstructor
public class OtherProperty {
String propertyName;
}
main
public class CopyMain {
public static void main(String[] args) {
ObjectDTO objectDTO = new ObjectDTO("DTO对象", 18, new OtherProperty("DTO其他属性"));
ObjectBO objectBO = new ObjectBO("BO对象",20,new OtherProperty("BO其他属性"),1.0);
BeanUtils.copyProperties(objectDTO,objectBO);
//ObjectDTO(name=DTO对象, age=18, otherProperty=OtherProperty(propertyName=DTO其他属性))
System.out.println(objectDTO);
//ObjectBO(name=DTO对象, age=18, otherProperty=OtherProperty(propertyName=DTO其他属性), value=1.0)
System.out.println(objectBO);
//true
System.out.println(objectDTO.getOtherProperty() == objectBO.getOtherProperty());
}
}
由此可见source会将target所有符合条件的属性进行copy,如果属性是引用对象,则会共享,属于浅克隆。
如果将ObjectDTO的name属性变更为null,otherProperty 设置为null,是否会跨过这些属性进行复制呢?
请看:
public static void main(String[] args) {
ObjectDTO objectDTO = new ObjectDTO(null, 18, null);
ObjectBO objectBO = new ObjectBO("BO对象",20,new OtherProperty("BO其他属性"),1.0);
BeanUtils.copyProperties(objectDTO,objectBO);
//ObjectDTO(name=null, age=18, otherProperty=null)
System.out.println(objectDTO);
//ObjectBO(name=null, age=18, otherProperty=null, value=1.0)
System.out.println(objectBO);
//true
System.out.println(objectDTO.getOtherProperty() == objectBO.getOtherProperty());
}
并不会跨过null进行赋值,而是会进行覆盖。所以童鞋们要注意这一点哦,不然在修改操作的方法可能导致重要数据的丢失。:)
聊了这么多,我们看看BeanUtils的源码是如何实现的吧!
/**
* Copy the property values of the given source bean into the given target bean.
* <p>Note: The source and target classes do not have to match or even be derived
* from each other, as long as the properties match. Any bean properties that the
* source bean exposes but the target bean does not will silently be ignored.
* @param source the source bean
* @param target the target bean
* @param editable the class (or interface) to restrict property setting to
* @param ignoreProperties array of property names to ignore
* @throws BeansException if the copying failed
* @see BeanWrapper
*/
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;
}
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);
}
}
}
}
}
}
细心的你,应该发现了这样的参数 @Nullable String… ignoreProperties,顾名思义BeanUtils支持跨过某些属性赋值。
public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {
copyProperties(source, target, null, ignoreProperties);
}
想完成上面的跨过name,otherProperty,只需要
BeanUtils.copyProperties(objectDTO,objectBO,"name","otherProperty");
思考一下,如果一直通过反射来取值赋值,那么这个时间成本其实是比较大的,如果返回大量的数据转成VO对象,那响应的速度是非常慢的。先测试一下BeanUtils的效率。
ObjectDTO objectDTO = new ObjectDTO("DTO", 18, new OtherProperty("property"));
ObjectBO objectBO = new ObjectBO();
ObjectBO objectBO1 = new ObjectBO();
ObjectBO objectBO2 = new ObjectBO();
long start = System.currentTimeMillis();
BeanUtils.copyProperties(objectDTO,objectBO);
long end1 = System.currentTimeMillis();
BeanUtils.copyProperties(objectDTO,objectBO1);
long end2 = System.currentTimeMillis();
BeanUtils.copyProperties(objectDTO,objectBO2);
long end3 = System.currentTimeMillis();
System.out.println("第一次复制花费的时间:" + (end1 - start));
System.out.println("第二次复制花费的时间:" + (end2 - end1));
System.out.println("第三次复制花费的时间:" + (end3 - end2));
结果如下:
第一次复制花费的时间:679
第二次复制花费的时间:0
第三次复制花费的时间:0
为什么出现这样的情况呢?在正常的预期中应该是 679*n 才对吧,让我们看看BeanUtils是怎么优化的。
请注意上面源码的这一行
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
只是一个简单的获得所有属性的PropertyDescriptor集合的方法。
/**
* Retrieve the JavaBeans {@code PropertyDescriptor}s of a given class.
* @param clazz the Class to retrieve the PropertyDescriptors for
* @return an array of {@code PropertyDescriptors} for the given class
* @throws BeansException if PropertyDescriptor look fails
*/
public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
return cr.getPropertyDescriptors();
}
继续深挖。
/**
* Create CachedIntrospectionResults for the given bean class.
* @param beanClass the bean class to analyze
* @return the corresponding CachedIntrospectionResults
* @throws BeansException in case of introspection failure
*/
@SuppressWarnings("unchecked")
static CachedIntrospectionResults forClass(Class<?> beanClass) throws BeansException {
CachedIntrospectionResults results = strongClassCache.get(beanClass);
if (results != null) {
return results;
}
results = softClassCache.get(beanClass);
if (results != null) {
return results;
}
results = new CachedIntrospectionResults(beanClass);
ConcurrentMap<Class<?>, CachedIntrospectionResults> classCacheToUse;
if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) ||
isClassLoaderAccepted(beanClass.getClassLoader())) {
classCacheToUse = strongClassCache;
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe");
}
classCacheToUse = softClassCache;
}
CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results);
return (existing != null ? existing : results);
}
原来BeanUtils会从强缓存中查找是否有当前类的属性集合,没有就去弱缓存中查找。如果都没有,才会进行创建创建。strongClassCache,softClassCache本质上其实是Map,class作为key,将创建的CachedIntrospectionResults作为value进行存储。源类也一样将PropertyDescriptor缓存到CachedIntrospectionResults中。因此大大提升了性能.
- 注意: 如果有父类的话,父类的属性也会缓存起来。
(想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!)