很多項(xiàng)目中都使用了VO、DTO、DO、PO等模型設(shè)計(jì),在每一層進(jìn)行參數(shù)傳遞之后,免不了會(huì)進(jìn)行大量的對(duì)象屬性之間的拷貝,此時(shí)我們會(huì)使用到BeanUtils這種工具類(lèi),使用copyProperties進(jìn)行便捷的拷貝,代碼如下:
實(shí)體類(lèi)定義
package com.brianxia.reflection.instropector;
/**
* @author brianxia
* @version 1.0
* @date 2021/3/17 19:28
*/
public class Source {
private String name;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.brianxia.reflection.instropector;
/**
* @author brianxia
* @version 1.0
* @date 2021/3/17 19:28
*/
public class Target {
private String name;
private long age;
public long getAge() {
return age;
}
public void setAge(long age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Target{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
引入相應(yīng)的依賴(lài)
這里我們選擇使用Spring和commons提供的BeanUtil
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.13.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
</dependencies>
方法調(diào)用測(cè)試
Source source = new Source();
source.setName("張三");
source.setAge(18);
Target target = new Target();
//Spring
BeanUtils.copyProperties(source,target);
//commons
BeanUtils.copyProperties(target,source);
源碼剖析
-
Spring實(shí)現(xiàn)
image.png
這是Spring的方法定義,這里有四個(gè)參數(shù),Spring對(duì)于復(fù)制屬性做了一些額外的處理:
source – 源bean
target – 目標(biāo)bean
editable – 可以限制只拷貝父類(lèi)或者接口中定義的屬性
ignoreProperties – 可變參數(shù)列表,排除某些特定的屬性
我們來(lái)看下Spring是如何實(shí)現(xiàn)的:
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;
}
如果傳遞了editable參數(shù),那么就以editable的Class為準(zhǔn),獲取屬性。
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
通過(guò)內(nèi)省,獲取屬性的信息。內(nèi)省在后文中詳細(xì)描述。我們?cè)敿?xì)來(lái)看getPropertyDescriptors這個(gè)方法:

這里使用了一種
CachedIntrospectionResults用來(lái)緩存曾經(jīng)使用過(guò)的內(nèi)省之后的數(shù)據(jù),否則每次進(jìn)行copy都需要重新獲取屬性信息,性能太低,所以使用進(jìn)行了緩存優(yōu)化。CachedIntrospectionResults定義了兩種ConcurrentHashMap存放屬性信息:
Spring會(huì)先從
strongClassCache中獲取,獲取不到再去softClassCache中獲取,如果都沒(méi)有獲取到,則進(jìn)行創(chuàng)建。創(chuàng)建是在CachedIntrospectionResults的構(gòu)造方法中,其實(shí)創(chuàng)建的過(guò)程就是將目標(biāo)類(lèi)的所有屬性的PropertyDescriptor進(jìn)行了緩存,注意: 如果有父類(lèi)的話(huà),父類(lèi)的屬性也會(huì)緩存起來(lái)。然后會(huì)將class作為key,將創(chuàng)建的CachedIntrospectionResults作為value,默認(rèn)緩存到strongClassCache屬性中(作為強(qiáng)引用)。源類(lèi)也一樣將PropertyDescriptor緩存到CachedIntrospectionResults中。因此大大提升了性能。
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);
}
這里有一個(gè)方法需要注意:ClassUtils.isCacheSafe,這個(gè)方法會(huì)檢查給定的beanClass是否由給定的classloader或者此classloader的祖先加載的(雙親委派的原理)。
所以第一次加載同一個(gè)類(lèi)的屬性會(huì)比較慢,后續(xù)使用緩存就不用重復(fù)加載了。
回到拷貝代碼部分,接下來(lái)就是比較常規(guī)的使用屬性信息獲取Read和Write方法,其實(shí)也就是獲取setter和getter方法,使用反射進(jìn)行調(diào)用:
for (PropertyDescriptor targetPd : targetPds) {
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 ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
這里Spring對(duì)于非public的方法進(jìn)行了setAccessible處理,使之有處理權(quán)限。但是Spring比較大的問(wèn)題是裝箱類(lèi)型Long和Integer等互相轉(zhuǎn)換無(wú)法做到:
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())
這個(gè)方法會(huì)判斷set和get的對(duì)象是否有繼承關(guān)系,如果沒(méi)有繼承關(guān)系,就直接返回。而方法內(nèi)部只是簡(jiǎn)簡(jiǎn)單單處理了基本數(shù)據(jù)類(lèi)型和裝箱類(lèi)型的關(guān)系,并未對(duì)數(shù)據(jù)進(jìn)行特殊的處理。
public static boolean isAssignable(Class<?> lhsType, Class<?> rhsType) {
Assert.notNull(lhsType, "Left-hand side type must not be null");
Assert.notNull(rhsType, "Right-hand side type must not be null");
if (lhsType.isAssignableFrom(rhsType)) {
return true;
} else {
Class resolvedWrapper;
if (lhsType.isPrimitive()) {
resolvedWrapper = (Class)primitiveWrapperTypeMap.get(rhsType);
return lhsType == resolvedWrapper;
} else {
resolvedWrapper = (Class)primitiveTypeToWrapperMap.get(rhsType);
return resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper);
}
}
}
整體來(lái)說(shuō),Spring對(duì)于copyProperties的實(shí)現(xiàn)過(guò)于簡(jiǎn)單,僅僅增加了ignore忽略和editable限制父類(lèi)拷貝等基礎(chǔ)功能,但未對(duì)復(fù)雜的數(shù)據(jù)類(lèi)型轉(zhuǎn)換做出特殊處理。接下來(lái)我們來(lái)看commons的實(shí)現(xiàn)。
-
commons實(shí)現(xiàn)
從整體結(jié)構(gòu)上來(lái)說(shuō),commons的實(shí)現(xiàn)做了很多的特殊處理提升性能。
image.png
它將數(shù)據(jù)類(lèi)型分成了三種,DynaBean(姑且稱(chēng)之為萬(wàn)能Bean,自行查詢(xún)下資料)、Map、JavaBean。
DynaBean使用較少,我們先說(shuō)Map的實(shí)現(xiàn):
@SuppressWarnings("unchecked")
final
// Map properties are always of type <String, Object>
Map<String, Object> propMap = (Map<String, Object>) orig;
for (final Map.Entry<String, Object> entry : propMap.entrySet()) {
final String name = entry.getKey();
if (getPropertyUtils().isWriteable(dest, name)) {
copyProperty(dest, name, entry.getValue());
}
}
直接從Map中遍歷Entry,然后將Entry的內(nèi)容寫(xiě)入到目標(biāo)對(duì)象中。而JavaBean的處理如下:
final PropertyDescriptor[] origDescriptors =
getPropertyUtils().getPropertyDescriptors(orig);
for (PropertyDescriptor origDescriptor : origDescriptors) {
final String name = origDescriptor.getName();
if ("class".equals(name)) {
continue; // No point in trying to set an object's class
}
if (getPropertyUtils().isReadable(orig, name) &&
getPropertyUtils().isWriteable(dest, name)) {
try {
final Object value =
getPropertyUtils().getSimpleProperty(orig, name);
copyProperty(dest, name, value);
} catch (final NoSuchMethodException e) {
// Should not happen
}
}
}
與Spring類(lèi)似,內(nèi)部維護(hù)了兩個(gè)WeakFastHashMap緩存屬性信息,WeakFastHashMap的設(shè)計(jì)很巧妙,借鑒了CopyOnWrite的思想,查詢(xún)數(shù)據(jù)時(shí)無(wú)需加鎖,它會(huì)選擇clone一份新的數(shù)據(jù)進(jìn)行修改,在clone出來(lái)的數(shù)據(jù)上進(jìn)行修改,然后再替換原來(lái)的數(shù)據(jù)。比如如下代碼:
@Override
public V get(final Object key) {
if (fast) {
return (map.get(key));
} else {
synchronized (map) {
return (map.get(key));
}
}
}
@Override
public void putAll(final Map<? extends K, ? extends V> in) {
if (fast) {
synchronized (this) {
final Map<K, V> temp = cloneMap(map);
temp.putAll(in);
map = temp;
}
} else {
synchronized (map) {
map.putAll(in);
}
}
}
commons對(duì)于數(shù)據(jù)類(lèi)型轉(zhuǎn)換,有專(zhuān)門(mén)的函數(shù)convertForCopy來(lái)進(jìn)行處理。
protected Object convert(final Object value, final Class<?> type) {
final Converter converter = getConvertUtils().lookup(type);
if (converter != null) {
log.trace(" USING CONVERTER " + converter);
return converter.convert(type, value);
} else {
return value;
}
}
首先根據(jù)數(shù)據(jù)類(lèi)型找到對(duì)應(yīng)的converter,比如IntegerConverter[UseDefault=true, UseLocaleFormat=false],使用裝飾者模式進(jìn)行增強(qiáng)。根據(jù)具體轉(zhuǎn)換出的類(lèi)型,使用Number進(jìn)行處理。
@Override
protected <T> T convertToType(final Class<T> targetType, final Object value) throws Throwable {
final Class<?> sourceType = value.getClass();
// Handle Number
if (value instanceof Number) {
return toNumber(sourceType, targetType, (Number)value);
}
// Handle Boolean
if (value instanceof Boolean) {
return toNumber(sourceType, targetType, ((Boolean)value).booleanValue() ? ONE : ZERO);
}
// Handle Date --> Long
if (value instanceof Date && Long.class.equals(targetType)) {
return targetType.cast(new Long(((Date)value).getTime()));
}
// Handle Calendar --> Long
if (value instanceof Calendar && Long.class.equals(targetType)) {
return targetType.cast(new Long(((Calendar)value).getTime().getTime()));
}
// Convert all other types to String & handle
final String stringValue = value.toString().trim();
if (stringValue.length() == 0) {
return handleMissing(targetType);
}
// Convert/Parse a String
Number number = null;
if (useLocaleFormat) {
final NumberFormat format = getFormat();
number = parse(sourceType, targetType, stringValue, format);
} else {
if (log().isDebugEnabled()) {
log().debug(" No NumberFormat, using default conversion");
}
number = toNumber(sourceType, targetType, stringValue);
}
// Ensure the correct number type is returned
return toNumber(sourceType, targetType, number);
}
比如此例中Long轉(zhuǎn)換成Integer類(lèi)型,只需要調(diào)用toNumber即可。針對(duì)Long型進(jìn)行特殊的處理。
// Long
if (targetType.equals(Long.class)) {
return targetType.cast(new Long(value.longValue()));
}
總結(jié):commons的實(shí)現(xiàn)要比Spring功能更加強(qiáng)大,不僅使用了具備COW技術(shù)的緩存大大增強(qiáng)并發(fā)讀取能力,同時(shí)對(duì)數(shù)據(jù)轉(zhuǎn)換做了嚴(yán)格的處理。
內(nèi)省
最后我們來(lái)說(shuō)一下Java中的內(nèi)省(Introspector)機(jī)制。Introspector與反射類(lèi)似,主要是對(duì)Java Bean屬性、方法等的一種處理方法。
PropertyDescriptor類(lèi):
PropertyDescriptor類(lèi)表示JavaBean類(lèi)通過(guò)存儲(chǔ)器導(dǎo)出一個(gè)屬性。主要方法:
1. getPropertyType(),獲得屬性的Class對(duì)象;
2. getReadMethod(),獲得用于讀取屬性值的方法;getWriteMethod(),獲得用于寫(xiě)入屬性值的方法;
3. hashCode(),獲取對(duì)象的哈希值;
4. setReadMethod(Method readMethod),設(shè)置用于讀取屬性值的方法;
5. setWriteMethod(Method writeMethod),設(shè)置用于寫(xiě)入屬性值的方法。Introspector類(lèi):
將JavaBean中的屬性封裝起來(lái)進(jìn)行操作。在程序把一個(gè)類(lèi)當(dāng)做JavaBean來(lái)看,就是調(diào)用Introspector.getBeanInfo()方法,得到的BeanInfo對(duì)象封裝了把這個(gè)類(lèi)當(dāng)做JavaBean看的結(jié)果信息,即屬性的信息。
getPropertyDescriptors(),獲得屬性的描述,可以采用遍歷BeanInfo的方法,來(lái)查找、設(shè)置類(lèi)的屬性。
手寫(xiě)一個(gè)copyProperties
package com.brianxia.reflection.instropector;
import org.springframework.beans.BeanUtils;
import javax.xml.ws.spi.Invoker;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author brianxia
* @version 1.0
* @date 2021/3/17 19:22
*/
public class InstropectorDemo {
public static Map<Class,Map<String,PropertyDescriptor>> sourcePd = new ConcurrentHashMap<>();
public synchronized static void createPd(Object source) throws IntrospectionException {
Class clazz = source.getClass();
if(!sourcePd.containsKey(clazz)){
sourcePd.put(clazz,new HashMap<>());
}
Map putData = sourcePd.get(clazz);
//獲取BeanInfo
BeanInfo beanInfo = Introspector.getBeanInfo(source.getClass());
//獲取屬性描述信息
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
putData.put(propertyDescriptor.getName(),propertyDescriptor);
}
}
public static PropertyDescriptor getPd(String name,Class clazz) {
if(!sourcePd.containsKey(clazz)){
return null;
}
return sourcePd.get(clazz).get(name);
}
/**
*
* @param source the source bean
* @param target the target bean
*/
public static void copyProperties(Object source,Object target) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
//獲取BeanInfo
BeanInfo beanInfo = Introspector.getBeanInfo(target.getClass());
//獲取屬性描述信息
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
Class<?> aClass = source.getClass();
//創(chuàng)建source的描述信息map
createPd(source);
//遍歷屬性描述信息,進(jìn)行copy
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
String name = propertyDescriptor.getName();
PropertyDescriptor sourcePd = getPd(name,aClass);
//如果source沒(méi)有對(duì)應(yīng)屬性,直接continue
if(sourcePd == null){
continue;
}
//獲取getter和setter方法
Method writeMethod = propertyDescriptor.getWriteMethod();
Method readMethod = sourcePd.getReadMethod();
//授予權(quán)限 private也可以訪(fǎng)問(wèn)
if(writeMethod != null && readMethod != null){
if(!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())){
writeMethod.setAccessible(true);
}
if(!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())){
readMethod.setAccessible(true);
}
//復(fù)制屬性
Object invoke = readMethod.invoke(source);
writeMethod.invoke(target,invoke);
}
}
}
public static void main(String[] args) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
Source source = new Source();
source.setName("張三");
source.setAge(18L);
Target target = new Target();
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
//target.setAge(source.getAge());
// target.setName(source.getName());
copyProperties(source,target);
//BeanUtils.copyProperties(source,target);
//org.apache.commons.beanutils.BeanUtils.copyProperties(target,source);
}
long end = System.currentTimeMillis();
System.out.println(end - start);
System.out.println(target);
}
}
此案例中類(lèi)型轉(zhuǎn)換并未特別處理,大家可以根據(jù)commons的實(shí)現(xiàn)自行處理簡(jiǎn)單的轉(zhuǎn)換。

