淺析BeanUtils
在聊聊BeanUtils之前,我們可以先了解一下PO VO BO DTO 。
- PO (persistant object持久對(duì)象)與數(shù)據(jù)庫中對(duì)應(yīng)的對(duì)象。
- BO (business object業(yè)務(wù)對(duì)象)處理業(yè)務(wù)邏輯肯定會(huì)涉及到多張表的數(shù)據(jù),BO可以將多個(gè)PO對(duì)象整合一起進(jìn)行業(yè)務(wù)處理。
- VO (view object表現(xiàn)層對(duì)象) 傳遞至前端的對(duì)象,可以將私密的數(shù)據(jù)隱藏掉。
- DTO (Data Transfer Object數(shù)據(jù)傳輸對(duì)象)可以簡(jiǎn)單理解為前端POST傳遞數(shù)據(jù)至后端,后端可以用@RequestBody ObjectDTO 對(duì)象進(jìn)行接收處理。
那么在業(yè)務(wù)比較復(fù)雜的情況下,必然會(huì)出現(xiàn)各種DTO,BO,VO,PO相互轉(zhuǎn)換的代碼。在業(yè)務(wù)代碼中出現(xiàn)大量的GET,SET方法不美觀,容易出錯(cuò)而且耗費(fèi)精力。由此出現(xiàn)了很多的開源工具,例如springframework的BeanUtils,apache的BeanUtils,dozer等。
本文討論的是springframework包下的BeanUtils。
import org.springframework.beans.BeanUtils;
BeanUtils.copyProperties(Object source, Object target);
? 就是把source對(duì)象的屬性賦值給target對(duì)象,但是要注意的是BeanUtils并不會(huì)對(duì)null進(jìn)行處理,而是會(huì)將其作為屬性值直接賦值給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對(duì)象", 18, new OtherProperty("DTO其他屬性"));
ObjectBO objectBO = new ObjectBO("BO對(duì)象",20,new OtherProperty("BO其他屬性"),1.0);
BeanUtils.copyProperties(objectDTO,objectBO);
//ObjectDTO(name=DTO對(duì)象, age=18, otherProperty=OtherProperty(propertyName=DTO其他屬性))
System.out.println(objectDTO);
//ObjectBO(name=DTO對(duì)象, age=18, otherProperty=OtherProperty(propertyName=DTO其他屬性), value=1.0)
System.out.println(objectBO);
//true
System.out.println(objectDTO.getOtherProperty() == objectBO.getOtherProperty());
}
}
由此可見source會(huì)將target所有符合條件的屬性進(jìn)行copy,如果屬性是引用對(duì)象,則會(huì)共享,屬于淺克隆。
如果將ObjectDTO的name屬性變更為null,otherProperty 設(shè)置為null,是否會(huì)跨過這些屬性進(jìn)行復(fù)制呢?
請(qǐng)看:
public static void main(String[] args) {
ObjectDTO objectDTO = new ObjectDTO(null, 18, null);
ObjectBO objectBO = new ObjectBO("BO對(duì)象",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());
}
并不會(huì)跨過null進(jìn)行賦值,而是會(huì)進(jìn)行覆蓋。所以童鞋們要注意這一點(diǎn)哦,不然在修改操作的方法可能導(dǎo)致重要數(shù)據(jù)的丟失。:)
聊了這么多,我們看看BeanUtils的源碼是如何實(shí)現(xiàn)的吧!
/**
* 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);
}
}
}
}
}
}
細(xì)心的你,應(yīng)該發(fā)現(xiàn)了這樣的參數(shù) @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");
思考一下,如果一直通過反射來取值賦值,那么這個(gè)時(shí)間成本其實(shí)是比較大的,如果返回大量的數(shù)據(jù)轉(zhuǎn)成VO對(duì)象,那響應(yīng)的速度是非常慢的。先測(cè)試一下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("第一次復(fù)制花費(fèi)的時(shí)間:" + (end1 - start));
System.out.println("第二次復(fù)制花費(fèi)的時(shí)間:" + (end2 - end1));
System.out.println("第三次復(fù)制花費(fèi)的時(shí)間:" + (end3 - end2));
結(jié)果如下:
第一次復(fù)制花費(fèi)的時(shí)間:679
第二次復(fù)制花費(fèi)的時(shí)間:0
第三次復(fù)制花費(fèi)的時(shí)間:0
為什么出現(xiàn)這樣的情況呢?在正常的預(yù)期中應(yīng)該是 679*n 才對(duì)吧,讓我們看看BeanUtils是怎么優(yōu)化的。
請(qǐng)注意上面源碼的這一行
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
只是一個(gè)簡(jiǎn)單的獲得所有屬性的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();
}
繼續(xù)深挖。
/**
* 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會(huì)從強(qiáng)緩存中查找是否有當(dāng)前類的屬性集合,沒有就去弱緩存中查找。如果都沒有,才會(huì)進(jìn)行創(chuàng)建創(chuàng)建。strongClassCache,softClassCache本質(zhì)上其實(shí)是Map,class作為key,將創(chuàng)建的CachedIntrospectionResults作為value進(jìn)行存儲(chǔ)。源類也一樣將PropertyDescriptor緩存到CachedIntrospectionResults中。因此大大提升了性能.
- 注意: 如果有父類的話,父類的屬性也會(huì)緩存起來。
參考CNblogs鏈接
[CSDN](https://blog.csdn.net/lz710117239/article/details/84781834)