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)


相关推荐

  • ny55 懒省事的小明

    ny55 懒省事的小明

  • Linux安装mysql5.7.26 –(傻瓜版3分钟搞定)

    Linux安装mysql5.7.26 –(傻瓜版3分钟搞定)前言在这之前的一天时间里,我全网搜mysql的各种安装方式,还有版本不同带来的问题,会发现在Mac或者在linux上安装5.7一下版本时,出现的问题会少很多,尤其是拿着dmg文件在Mac安装就是1分钟的事,但是在linux安装5.7时出现了不少的问题,出现的问题各式各样,大家安装时碰到问题了,一定要找你当前版本下的解决方式。严格按照本文步骤可以顺利安装,这也是我连续在三…

  • 使用微软官方工具下载安装Windows10系统

    使用微软官方工具下载安装Windows10系统准备安装盘首先准备一个8G以上容量的U盘,并从微软官网下载Windows10下载工具:https://www.microsoft.com/zh-cn/software-download/windows10注意在执行下面操作之前备份好您U盘上的数据,因为下面的操作会清空该U盘上的所有数据。制作过程双击打开下载到的MediaCreationTool1809.exe文件,弹出Wi…

  • 抖音数据统计_抖音直播带货数据分析(最新教程)

    抖音数据统计_抖音直播带货数据分析(最新教程)现在直播带货是一个热门趋势,它可以突破抖音挂购物车数量的限制,已经有不少商家通过直播带货实现流量变现了。那么,如何做好抖音直播就成了抖音电商玩家最大的需求。为此,飞瓜数据总结了几个抖音直播电商数据分析的维度和需要关注的关键指标:一.抖音直播电商数据分析的维度抖音直播电商数据分析需要围绕“带货”这个核心目标展开,这其中就涉及到“人、货、场”这三个概念,也就是抖音直播的流量、商品和直播间。这三个概念组…

  • sql报错将截断字符串或二进制数据_sql根据分隔符截取字符串

    sql报错将截断字符串或二进制数据_sql根据分隔符截取字符串今天使用数据库的时候,遇见这样的错误:成因分析:自己在设计数据库的时候,将表的某些属性的域的长度设置的小了:而我在填写的对应的数据长度是超过了数据库属性长度的设计,这样,在将数据录入数据库的时候,会将数据截断。解决方案:扩充数据库对应属性的长度:~~~~~~~~~~完美解决了~~~~~~~~~~~~~~~~~~~~

  • 怎么自定义服务器的404,如何自定义404页面

    怎么自定义服务器的404,如何自定义404页面404错误页面是WWW网站访问比较经常出现的错误。大家最熟悉的也是最常见的出错提示:404notfound。404页面就是当用户输入了错误的链接时,返回的页面。而默认的404错误页面呆板麻木,让访问者感觉很挫败,可能会直接离开您的网站。自定义404页面的目的是:告诉浏览者其所请求的页面不存在或链接错误,同时引导用户使用网站其他页面而不是关闭窗口离开。是增强用户体验的很好的做法。简而言之,有两点…

发表回复

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

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