使用Spring的BeanUtils进行对象拷贝很容易。
首先引入响应的jar包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
使用时代码就一行:
org.springframework.beans.BeanUtils.copyProperties(orig, dest);
对应形参:Object source, Object target
参数表示要把orig里面的属性值拷贝到dest对象中。
注意,对象拷贝的是属性值的引用,如果是基础数据类型还好,如果是一个对象类型,拷贝完成后,orig里面的对象类型属性值发生变化,dest里面相应的属性值会发生变化。会有一定的风险。
查看spring源码,方法实现代码如下:
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;
}
//获取target对象的PropertyDescriptor属性数组targetPds
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
//遍历target对象属性
for (PropertyDescriptor targetPd : targetPds) {
//获取其set方法
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
//获取source对象与target对象targetPd属性同名的PropertyDescriptor对象sourcePd
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
//获取source对应属性的get方法
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
//通过反射获取source对象属性的值
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
//通过反射给target对象属性赋值
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
执行过程比较简单,底层对象取值、赋值还是用的反射。
在看获取对象方法列表之前,先看一下属性描述类是干嘛的。java.beans.PropertyDescriptor,是jdk提供的一个bean相关的类,它封装了一个Javabean中属性相关的信息,比如属性名称name、get方法名readMethodName、set方法名writeMethodName、get方法readMethodRef、write方法writeMethodRef等信息。
而方法的类型为java.beans.MethodRef,里面用到了java.lang.ref.SoftReference和java.lang.ref.WeakReference。
其中java.lang.ref.SoftReference的API描述为:软引用对象,在响应内存需要时,由垃圾回收器决定是否清除此对象。软引用对象最常用于实现内存敏感的缓存。 这个对象是放在jvm内存中的,后面会讲到。可能会被jvm清除。
java.lang.ref.WeakReference的API描述:弱引用对象,它们并不禁止其指示对象变得可终结,并被终结,然后被回收。弱引用最常用于实现规范化的映射。
继续看获取对象属性描述列表的方法:
org.springframework.beans.BeanUtils#getPropertyDescriptors(Class<?> clazz)
org.springframework.beans.BeanUtils#getPropertyDescriptor(Class<?> clazz, String propertyName)
第一个获取的是类属性列表,第二个是根据属性名称获取其具体描述。
public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException {
CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz);
return cr.getPropertyDescriptors();
}
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;
}
//根据class创建CachedIntrospectionResults对象
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);
}
private CachedIntrospectionResults(Class<?> beanClass) throws BeansException {
try {
if (logger.isTraceEnabled()) {
logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]");
}
//获取BeanInfo对象
this.beanInfo = getBeanInfo(beanClass);
if (logger.isTraceEnabled()) {
logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]");
}
this.propertyDescriptorCache = new LinkedHashMap<>();
//将BeanInfo中的PropertyDescriptor数组放入map
// This call is slow so we do it once.
PropertyDescriptor[] pds = this.beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (Class.class == beanClass &&
("classLoader".equals(pd.getName()) || "protectionDomain".equals(pd.getName()))) {
// Ignore Class.getClassLoader() and getProtectionDomain() methods - nobody needs to bind to those
continue;
}
if (logger.isTraceEnabled()) {
logger.trace("Found bean property '" + pd.getName() + "'" +
(pd.getPropertyType() != null ? " of type [" + pd.getPropertyType().getName() + "]" : "") +
(pd.getPropertyEditorClass() != null ?
"; editor [" + pd.getPropertyEditorClass().getName() + "]" : ""));
}
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
this.propertyDescriptorCache.put(pd.getName(), pd);
}
// Explicitly check implemented interfaces for setter/getter methods as well,
// in particular for Java 8 default methods...
Class<?> clazz = beanClass;
while (clazz != null && clazz != Object.class) {
Class<?>[] ifcs = clazz.getInterfaces();
for (Class<?> ifc : ifcs) {
if (!ClassUtils.isJavaLanguageInterface(ifc)) {
for (PropertyDescriptor pd : getBeanInfo(ifc).getPropertyDescriptors()) {
if (!this.propertyDescriptorCache.containsKey(pd.getName())) {
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
this.propertyDescriptorCache.put(pd.getName(), pd);
}
}
}
}
clazz = clazz.getSuperclass();
}
this.typeDescriptorCache = new ConcurrentReferenceHashMap<>();
}
catch (IntrospectionException ex) {
throw new FatalBeanException("Failed to obtain BeanInfo for class [" + beanClass.getName() + "]", ex);
}
}
//获取BeanInfo对象,主要是Introspector.getBeanInfo
private static BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanClass);
if (beanInfo != null) {
return beanInfo;
}
}
return (shouldIntrospectorIgnoreBeaninfoClasses ?
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
Introspector.getBeanInfo(beanClass));
}
从以上的代码可以看出,Spring底层是通过BeanInfo.getBeanInfo(Class<?> beanClass)获取到BeanInfo对象,然后将其属性放入到jvm内存,每当程序再次请求时,会优先从内存中读取。