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中的属性描述器加入到子类中,最后统一遍历完成复制。

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • Python中的三目表达式

    Python中的三目表达式Python中的三目表达式一般C系列语言,例如C#,三目运算都是“?:”的结构。例如:res=(i>j?true:false);但是在python中,使用的是if-else来实现的res=Trueifi>jelse2;#如果条件为真的话,那么结果为前者,否则为后者

    2022年10月27日
  • android组件安全检测工具(内存检测工具memtest)

    Android应用安全检测工具简介1、测试工具集Appie–轻量级的软件包,可以用来进行基于Android的渗透测试,不想使用VM的时候可以尝试一下.AndroidTamer–可以实时监控的虚拟环境,可以用来进行一系列的安全测试,恶意软件检测,渗透测试和逆向分析等.AppUse–AppSecLabs开发的Android的虚拟环境.Mobisec–移…

  • DatabaseMetaData,ResultSet,ResultSetMetaData「建议收藏」

    DatabaseMetaData,ResultSet,ResultSetMetaData「建议收藏」DatabaseMetaData,ResultSet,ResultSetMetaData三种类型的区别    出处:http://blog.csdn.net/suwu1501、DatabaseMetaData   有关整个数据库的信息:数据库产品的名称和版本,数据库中表和列等信息,关于数据库的整体综合信息。   接口关系:publicinterfaceDatabaseMetaD…

  • oracle sysdate毫秒,Oracle sysdate常用「建议收藏」

    oracle sysdate毫秒,Oracle sysdate常用「建议收藏」SELECT*FROM(SELECT*FROMcalenderDetailAORDERBYa.calenderdesc)WHERErownum<3;//计算时间大于当前时间的最近2条记录取当前小时,以及相隔一小时v_begin_date:=to_date(to_char(sysdate,‘yyyy-mm-ddhh24‘),‘yyyy-mm-ddhh24‘);…

    2022年10月24日
  • Ubuntu下安装eclipse

    Ubuntu下安装eclipse

  • pycharm 安装第三方库指南

    pycharm 安装第三方库指南当pycharm中没有你想要的安装包时,需要自己在pypi中下载或是其他方式下载,清华镜像网站等;只用alt+f12打开pycharm控制台,然后将下载的文件放在显示的目录里下载即可。

发表回复

您的电子邮箱地址不会被公开。

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