源码解析 BeanUtils,Java 开发还不会这些

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

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

133 行中可以看到会进入 ExtendedBeanInfoFactory 的 getBeanInfo 方法:

我们继续看 supports 方法:

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


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

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

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

直接查看 Introspector.getBeanInfo 方法:

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


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


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

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

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

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

上图中可以看到主要构造了 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 位开始截取。
继续往下看:

当方法参数个数为 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 行的代码基本很少用到,这里我们直接跳过,继续往下看:

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


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


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

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

最后将所有的属性以属性名称为 key,PropertyDescriptor 为 value 放入 properties 中。
我们继续回到 getTargetPropertyInfo 方法,往下看:

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

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

这里是将 PropertyDescriptor 这个类封装为它的子类?GenericTypeAwarePropertyDescriptor。
然后将其放入 propertyDescriptorCache 中,到这里 CachedIntrospectionResults 的构造方法已经分析完成。
我们继续看 CachedIntrospectionResults.forClass 方法:

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


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

可以看到,这里首先 targetPds 这个变量接受返回的 pds 数组,然后对 targetPds 数组进行遍历。
这里我们重点看 365 行的 getPropertyDescriptor 方法:

上图中可以看到它和 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 方法的源码就解析完成。
评论