Spring中BeanUtils.copyProperties源码分析

使用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内存,每当程序再次请求时,会优先从内存中读取。

    原文作者:Spring Cloud
    原文地址: https://blog.csdn.net/SJZYLC/article/details/81284386
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞