Spring中BeanUtils.copyProperties源码分析

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】
免费领取10G资料包与项目实战视频资料

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


来源:http://ddrv.cn/a/88268

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » Spring中BeanUtils.copyProperties源码分析

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏