在聊聊BeanUtils之前,我们可以先了解一下PO VO BO DTO 。
那么在业务比较复杂的情况下,必然会出现各种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。
例如:
@Data @AllArgsConstructor public class ObjectDTO { String name; int age; OtherProperty otherProperty; } 复制代码
@Data @AllArgsConstructor public class ObjectBO { String name; int age; OtherProperty otherProperty; double value; } 复制代码
@Data @AllArgsConstructor public class OtherProperty { String propertyName; } 复制代码
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中。因此大大提升了性能.