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数组拼接字符串_Python练习题——数组拼接##输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。##示例1:#输入:[10,2]#输出:”102″##示例2:#输入:[3,30,34,5,9]#输出:”3033459″##1#classSolution:#defminNumber(self,nums):#nums_str=[str(i)…

  • ScriptManager.RegisterStartupScript方法[通俗易懂]

    ScriptManager.RegisterStartupScript方法[通俗易懂]出处:忘记了,致歉作者 如果页面中不用Ajax,cs中运行某段js代码方式可以是:Page.ClientScript.RegisterStartupScript(Page.GetType(),””,”window.open(‘default2.aspx’)”);如果页面中使用了Ajax,则上述代码即使执行也无效果。对这种情况我们通常采用:ScriptManag

  • kvm网卡模式_java软中断原理

    kvm网卡模式_java软中断原理玩转KVM: 了解网卡软中断RPS

  • Python中break和continue的区别

    Python中break和continue的区别大部分人总是会搞混break和continue,虽然他们都是结束循环,但是结束的方式并不一样。break用于结束整个循环。continue用于结束当前循环。**1.**break有时候我们写代码时想让它结束整个循环,除了条件达到False结束,我们可以设定一个条件,当他达到这个条件时,结束整个循环。break用于完全跳出循环,执行循环体后面的语句。whileTrue:s=i…

  • golang 设置ip地址_post请求header设置

    golang 设置ip地址_post请求header设置直接贴代码packagemainimport( “io” “net/http” “encoding/json”)typeKPIstruct{ Fziint`json:”fenzi”`//分子 Fmuint `json:”fenmu”` //分母}funckpi(whttp.ResponseWriter,r*http.Request){ kk…

  • 共享格子售货机方案/案列/APP/小程序/项目

    共享格子售货机方案/案列/APP/小程序/项目现代化的共享格子售货机可以说都是自动售货机应用软件开发使用,不仅支持纸币硬币等现金支付,还可以支持微信、支付宝、百度钱包等手机支付,甚至可以支持银联卡、员工卡、学生卡等各种刷卡支付,除此之外可可以远程监控,不需要运营人员亲自到现场就能知道自动售货机的运营数据。以上各种功能不仅大大方便了大家在售货机上购买东西,也方便了商家的自动售货机运营。目录一、共享格子售货机方案介绍二、共享格子售货机方案优…

发表回复

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

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