beanutils.copyproperties原理_beanutils工具类

beanutils.copyproperties原理_beanutils工具类常用的BeanUtils.copyProperties方法,你知道它的实现原理吗?

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

       BeanUtils.copyProperties 方法在项目中的使用非常频繁,但我们对它知之甚少,在一次使用中,我遇到了下面的这种情况,直接上代码:

public class ParentSrc {
    private String attr;

    public String getAttr() {
        return attr;
    }

    public void setAttr(String attr) {
        this.attr = attr;
    }
}
public class Src extends ParentSrc {
    private String subAttr;

    public String getSubAttr() {
        return subAttr;
    }

    public void setSubAttr(String subAttr) {
        this.subAttr = subAttr;
    }
}
public class ParentTarget {
    private String attr;

    public String getAttr() {
        return attr;
    }

    public void setAttr(String attr) {
        this.attr = attr;
    }
}
public class Target extends ParentTarget {
    private String subAttr;

    public String getSubAttr() {
        return subAttr;
    }

    public void setSubAttr(String subAttr) {
        this.subAttr = subAttr;
    }

    @Override
    public String toString() {
        return "Target{" +
                "attr='" + getAttr() + '\'' +","+
                "subAttr='" + subAttr + '\'' +
                '}';
    }
}
public class Test {
    public static void main(String[] args) {
        Src src=new Src();
        src.setSubAttr("subAttr");
        src.setAttr("attr");
        Target target = new Target();
        BeanUtils.copyProperties(src,target);
        System.out.println(target);
    }
}

     代码中我们可以看到,我将src的属性复制给target 。那么 src 的父类 ParentSrc 的属性 attr 能否复制给 target 的父类 ParentTarget 呢 ?答案是可以。但我的第一反应是不确定,所以我决定看一下它的源码是如何实现的,直接看 BeanUtils 中的源码 :

beanutils.copyproperties原理_beanutils工具类

beanutils.copyproperties原理_beanutils工具类

源码中我们可以看到,editable 和 ignoreProperties 为空,直接忽略。重点看356行 getPropertyDescriptors 方法:

beanutils.copyproperties原理_beanutils工具类

上图中可以看到会通过target的Class属性构造一个 CachedIntrospectionResults 类,然后获取其 PropertyDescriptor的值,我们直接看 forClass 方法:

beanutils.copyproperties原理_beanutils工具类

上图中可以看到第一次通过target的class获取的 results 肯定为空,所以我们直接看重点,第70行的代码如何构造 CachedIntrospectionResults :

 

beanutils.copyproperties原理_beanutils工具类

上图中,我们先忽略141-143行的日志打印代码,重点看145行的 getBeanInfo 方法:

beanutils.copyproperties原理_beanutils工具类

上图中的这段代码,我们需要详细解释下,首先我们看下 beanInfoFactories 是如何进行初始化的:

beanutils.copyproperties原理_beanutils工具类

beanutils.copyproperties原理_beanutils工具类

beanutils.copyproperties原理_beanutils工具类

beanutils.copyproperties原理_beanutils工具类

这里我们不详细解释它的初始化过程,重点代码我已经红框圈出来,主要就是先通过classpath下的 META-INF/spring.factories 文件获取  org.springframework.beans.BeanInfoFactory 为key的值,如下图:

beanutils.copyproperties原理_beanutils工具类

然后初始化 org.springframework.beans.ExtendedBeanInfoFactory 这个类,将其放入 beanInfoFactories 中,所以它只有ExtendedBeanInfoFactory 这一个类,所以我们继续往下看  getBeanInfo 方法:

beanutils.copyproperties原理_beanutils工具类

133行中可以看到会进入 ExtendedBeanInfoFactory 的 getBeanInfo 方法:beanutils.copyproperties原理_beanutils工具类

我们继续看 supports 方法:

beanutils.copyproperties原理_beanutils工具类

这里会获取target的所有方法,去30行判断返回什么,直接看 ExtendedBeanInfo.isCandidateWriteMethod 方法:

beanutils.copyproperties原理_beanutils工具类

beanutils.copyproperties原理_beanutils工具类

重点在94的判断,这里首先需要方法名称的长度大于3 且 方法名称是以set开头 且 (方法的返回类型是非void的 或 方法是静态的),到这里就很明显了,我们通常的实体类中的set方法的返回类型一定是void的和非静态的,所以  ExtendedBeanInfo.isCandidateWriteMethod 会返回false, 继续往下看 supports 也会返回 false ,所以 ExtendedBeanInfoFactory 的  getBeanInfo 方法返回空。所以我们继续往下看上图中的 CachedIntrospectionResults 的 getBeanInfo 方法:

beanutils.copyproperties原理_beanutils工具类

第一次循环结束, beanInfo == null 为true,所以进入while中的第二次循环,128行中可以看到 var1.hasNext() 已经没有下一个了,所以返回false。继续看 shouldIntrospectorIgnoreBeaninfoClasses 属性的初始化过程:

beanutils.copyproperties原理_beanutils工具类

这里不详细解释,主要是从classpath下的  spring.properties 文件中获取 spring.beaninfo.ignore 这个key的值,通常 spring.properties 这个文件不存在,所以 shouldIntrospectorIgnoreBeaninfoClasses 属性默认为 false,所以继续往下走:

beanutils.copyproperties原理_beanutils工具类

直接查看 Introspector.getBeanInfo 方法:

beanutils.copyproperties原理_beanutils工具类

上图中可以看到 首先  ReflectUtil.isPackageAccessible 返回true, 继续往下走,第一次通过beanClass 获取 beanInfo 肯定为空,所以直接看173行的重点代码,首先看 Introspector的初始化过程:

beanutils.copyproperties原理_beanutils工具类

beanutils.copyproperties原理_beanutils工具类

这里的 stopClass为null ,flag 为 USE_ALL_BEANINFO ,继续看397-399行的代码,不多解释, explicitBeanInfo 最后获取的为 null。重点看401-407行的代码,首先会获取target的父类 superClass ,然后会将superClass 作为参数传进去通过 getBeanInfo 方法获取对应的beanInfo 信息赋值给 superBeanInfo 。这里就是为什么src的父类属性可以复制给 target 的父类的原因。

我们继续往下看 Introspector 的 getBeanInfo() 方法:beanutils.copyproperties原理_beanutils工具类

beanutils.copyproperties原理_beanutils工具类

这里我们重点看428行的 getTargetPropertyInfo 方法,为什么?看下图

beanutils.copyproperties原理_beanutils工具类

由上文和上图中的151行我们可以推断出:BeanUtils.copyProperties 就是使用 PropertyDescriptor 这个属性来完成2个实体类之间的属性复制。

我们继续看getTargetPropertyInfo 方法 

beanutils.copyproperties原理_beanutils工具类

上文我们已经分析过了 explicitBeanInfo 为null,所以直接往下看 465-467行的代码,由上文分析,这里 superBeanInfo 不为null, 所以这里会将父类的属性也一并加入到子类中。继续往下看这里的 additionalBeanInfo 和  explicitProperties 都为空,直接看483行以后的代码:

beanutils.copyproperties原理_beanutils工具类

上图中可以看到首先会获取beanClass的所有public方法,然后会过滤掉静态方法和方法名称长度小于3且方法名称不是以is开头的method。继续往下看,重点在512行,这是最常用到的代码。它首先判断方法参数的个数为0(也就是没有方法参数),然后判断方法名称是以get开头的,就会构造一个 PropertyDescriptor 类,我们来看一下他的属性:

beanutils.copyproperties原理_beanutils工具类

上图中可以看到主要构造了5个属性,以本文的demo为例:

Class0 : Target、name : subAttr、readMethod : getSubAttr、writeMethod : null、baseName : SubAttr

Class0为当前目标类Target,name为方法名称subAttr,readMethod为目标类的获取属性值方法getSubAttr,baseName为首字母大写的方法名称SubAttr,由于是get方法开头的,所以WriteMethod为空。

我们继续看上图中的 getTargetPropertyInfo 方法的515行,若属性为布尔类型的,方法名称就会从第2位开始截取。

继续往下看:

beanutils.copyproperties原理_beanutils工具类

当方法参数个数为1时,我们重点看520-522行,这是我们最经常用到的代码。首先判断方法的返回类型为 void 且方法名称是set开头的,就会构造一个专属set方法的 PropertyDescriptor ,它的5个属性为:

Class0 : Target、name : subAttr、readMethod : null、writeMethod : setSubAttr、baseName : SubAttr

可以看出来,它与get方法构造的PropertyDescriptor基本一样,就是readMethod 和 writeMethod 分别对应get 和 set 方法而已。527-534行的代码基本很少用到,这里我们直接跳过,继续往下看:

beanutils.copyproperties原理_beanutils工具类

我们重点先看549行的 addPropertyDescriptor 方法:

beanutils.copyproperties原理_beanutils工具类

beanutils.copyproperties原理_beanutils工具类

上图中可以看到,首先会从 pdStore 中通过属性名称获取 PropertyDescriptor 的集合,若集合为空,则直接创建一个新的集合,将其放入pdStore中。这里主要用来属性方法名称相同的 PropertyDescriptor ,通常这里存储的就是get和set方法的属性集合。继续往下看,由于beanClass 等于 class0 ,所以583-607行的代码不做分析。那么最后 pdStore 中存储的key是方法名称propName ,value 是长度为二的PropertyDescriptor的集合。这里的addPropertyDescriptor方法就分析结束。

继续往下看processPropertyDescriptors 方法:

beanutils.copyproperties原理_beanutils工具类

beanutils.copyproperties原理_beanutils工具类

这个方法的代码很长,我们挑红框里重点的说,其中有2个for循环,其中会将变量 gpd 对应 ReadMethod 属性描述,spd 对应 WriteMethod 属性描述,继续往下看:

beanutils.copyproperties原理_beanutils工具类

这里是将ReadMethod 和 WriteMethod 合并为一个 PropertyDescriptor,继续往下看:

beanutils.copyproperties原理_beanutils工具类

最后将所有的属性以属性名称为key,PropertyDescriptor 为value放入 properties 中。

我们继续回到 getTargetPropertyInfo 方法,往下看:

beanutils.copyproperties原理_beanutils工具类

最后将 properties 的value 作为数组赋值给 result 返回。到这里我们的 getBeanInfo 的方法就分析结束了,我们继续看CachedIntrospectionResults 的构造方法:

beanutils.copyproperties原理_beanutils工具类

我们重点看161和162行代码,首先是beanClass!=Class的,所以条件判断为true。

我们继续看 buildGenericTypeAwarePropertyDescriptor 方法:

beanutils.copyproperties原理_beanutils工具类

这里是将 PropertyDescriptor 这个类封装为它的子类 GenericTypeAwarePropertyDescriptor。

然后将其放入 propertyDescriptorCache 中,到这里CachedIntrospectionResults的构造方法已经分析完成。

我们继续看CachedIntrospectionResults.forClass 方法:

beanutils.copyproperties原理_beanutils工具类

这里的72行首先判断当前beanClass 是否和 CachedIntrospectionResults 使用同一个类加载器加载的,这里通常都是使用AppClassLoader 进行加载的,所以 if 判断为false , classCacheToUse 为 strongClassCache,最后将beanClass 和 results 关联起来做缓存,便于在下一次同样的类做属性复制时,可以直接从strongClassCache的缓存中取。

到这里CachedIntrospectionResults.forClass就分析完成,继续看 getPropertyDescriptors 方法:

beanutils.copyproperties原理_beanutils工具类

beanutils.copyproperties原理_beanutils工具类

这段代码很简单,不多做解释,主要是将propertyDescriptorCache 中的value转存储到 PropertyDescriptor 数组 pds中,然后返回。到这里我们的准备工作基本完成,我们最后回到 BeanUtils.copyProperties 方法中:

beanutils.copyproperties原理_beanutils工具类可以看到,这里首先 targetPds 这个变量接受返回的pds数组,然后对targetPds数组进行遍历。

这里我们重点看365行的 getPropertyDescriptor 方法:

beanutils.copyproperties原理_beanutils工具类

上图中可以看到它和356行的 getPropertyDescriptors 方法基本一样,主要功能就是通过获取source class的 PropertyDescriptor ,然后通过目标类的属性名获取对应的 PropertyDescriptor,这样就可以获取到对应属性的readMethod 。在本文的demo中,source class 为Src , 目标类的属性名subAttr。

我们继续往下看368行,这里会判断目标类写方法的参数类型是否和源类的读方法的返回类型相同或者为其父类,在本demo中writeMethod.getParameterTypes()[0] 为 Target 中  setSubAttr 方法的参数类型String, readMethod.getReturnType() 为 Src类中getSubAttr 方法的返回类型String,所以它们是相同的ClassUtils.isAssignable 的判断为true。继续往下看读方法的声明类标识符通常是 public的,375行的写方法的声明类的标识符通常也是public。

核心在374行和379行的代码, readMethod 通过反射调用Src 类的getSubAttr 方法获取其对应的属性值value(在本demo中value为subAttr),最后writeMethod 通过反射调用 Target类中的 setSubAttr 方法成功将Src的属性值复制给 subAttr属性

到这里 BeanUtils.copyProperties 方法的源码就解析完成。

总结:

1. 看完整个 BeanUtils.copyProperties 方法,去掉表层的封装和缓存优化后,核心就是先通过反射获取 Target 目标类的属性集合,然后遍历属性集合通过属性名称获取 Src 源类中的属性描述器,反射调用将值复制给目标类中的属性。

2. 父类的属性在 Introspector 构造类中会获取父类的实体信息 superBeanInfo ,最后会将superBeanInfo中的属性描述器加入到子类中,最后统一遍历完成复制。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/186363.html原文链接:https://javaforall.net

(0)
上一篇 2022年10月3日 下午3:46
下一篇 2022年10月3日 下午3:46


相关推荐

  • 邀请函丨AI投研再思考:从"玩具虾"到"工具虾"鸿沟在哪?——“信量”系列主题交流会第六期

    邀请函丨AI投研再思考:从"玩具虾"到"工具虾"鸿沟在哪?——“信量”系列主题交流会第六期

    2026年3月17日
    2
  • 086-vdbench

    086-vdbench【1】centos下的安装首先安装JavaJDK和一些工具包sudoyuminstall-yjava-1.7.0-openjdkjava-1.7.0-openjdk-develunzip./vdbench-t【2】vdbench是一个I/O工作负载生成器,用于验证数据完整性和度量直接附加和网络连接的存储的性能。它是一个免费的工具,容易使用,而且常常用于测试和基准测试。…

    2022年5月19日
    49
  • Nginx正向代理配置详解

    Nginx正向代理配置详解一 nginx 正向代理介绍及配置 需要在客户端配置代理服务器进行指定网站访问 模块 ngx http proxy module http nginx org en docs http ngx http proxy module html proxy set header1 环境介绍代理服务器系统环境为 centosnginx 代理服务器为 192 168 10 10 测试客户端为局域网内任意 windows 电脑或 Linux 电脑 2 正向代理简介通过代理服务器来访问

    2026年3月26日
    2
  • WebStorm 7.0 注冊码

    WebStorm 7.0 注冊码

    2021年12月4日
    49
  • mybatis批量夺标添加_Mybatis批量删除多表

    mybatis批量夺标添加_Mybatis批量删除多表一 这里主要考虑两种参数类型 数组或者集合 而这点区别主要体现在 EmpMapper xml 文件中标签的 collection 属性 当 collection array 时 表名参数为数组 当 collection list 时 表名参数为集合 二 注意 无论 Mybatis 是与 MysqL 数据库结合 还是与 Oracle 数据库 都同样适合如下设置与操作 三 具体示例如下 EmpMapper xml

    2026年3月26日
    2
  • Blender真是不错的东西

    Blender真是不错的东西上个礼拜拿到了一本 原来一直想学一下这个 3d 开发工具中最牛的开源 一直没什么时间就放了下来 拿到这本之后欣喜之下翻了起来 Blender 的强大就不多说了 绝对算得上水准 对于我这个玩过 max 和 maya 的人类来说 对于我这个初学者来说 装上 blender 有种不知所

    2026年3月26日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号