Spring提供的BeanUtils源码剖析(附手写copyProperties方法)
Posted 张子行的博客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring提供的BeanUtils源码剖析(附手写copyProperties方法)相关的知识,希望对你有一定的参考价值。
前言
前段时间刚入职xxx公司,由于公司内部有一套自己的技术架构,并且很多东西都是自研、封装的。身为小菜鸡的在业余时间抽空看了一下里面的源码,感觉这些人基础挺扎实的哈哈哈哈,这些个基础架构工具包全是自研的,牛逼。一下子激发了我看 BeanUtils 源码的兴趣,想着靠自己的摸索,结合之前研究的JDK动态代理技术,自己也尝试着写一套功能类似的基架组件出来(结合代理技术的以后再写,这玩意有点繁琐)。
先导知识之JAVA内省
不清楚JAVA内省的小伙伴,先来了解一下这方面的知识,何为内省?内省是JAVA提供的一组可以操纵 JavaBean 属性的一组 Api,掌握了这些个 Api 我们也可以写一个自己的 BeanUtils 出来。下面先来介绍一波这些个 Api。
- Introspector.getBeanInfo(o.getClass()) 获取当类的一个BeanInfo对象,和Spring中的 BeanDefinition 对象有点类似,BeanDefinition 是封装了 Bean 信息的一个信息对象,BeanInfo 是封装了 Java 对象一系列 Api 方法的对象。
- BeanInfo.getPropertyDescriptors() 获取 PropertyDescriptor 对象,可以通过此对象获取类字段属性 name、value、get和set方法名字、对字段进行属性填充。等操作
山寨版 BeanUtils.copyProperties()
恭喜你已经通过了入职考核,有资格进入 zzh 盗版集团来,我们集团这里的人都是人中翘楚、人中龙凤、国之栋梁、梁之中流砥柱、柱中之无懈可击的存在了。下面列举一个 我司开发的山寨版BeanUtils.copyProperties()软件 给读者参观(),温馨提示:前方500米有宝箱,请大家仔细翻阅。大概流程为:遍历操作对源对象与目标对象进行属性名的匹配,匹配上了,随即进行对应属性名的value值填充。
public class IntrospectionTest
public static List<Object> init()
List<Object> zzhSources = new ArrayList<>();
ZzhSource zzhSource = new ZzhSource();
zzhSource.setAge("1");
zzhSource.setName("zzh1");
ZzhSource zzhSource2 = new ZzhSource();
zzhSource2.setAge("2");
zzhSource2.setName("zzh2");
zzhSources.add(zzhSource);
zzhSources.add(zzhSource2);
return zzhSources;
@SneakyThrows
public static void main(String[] args)
List<Object> zzhTargets = new ArrayList<>();
System.err.println("copy before: " + zzhTargets);
copyProperties(init(), zzhTargets, ZzhTarget.class);
System.err.println("copy after:: " + zzhTargets);
public static void copyProperties(List<Object> sources, List<Object> targets, Class targetClass)
sources.stream().forEach(source ->
try
Object o = targetClass.newInstance();
Arrays.stream(Introspector.getBeanInfo(source.getClass()).getPropertyDescriptors()).forEach(sp ->
try
/**
* 遍历操作:targetClass 对应的对象与 sources 集合中对应的对象进行属性匹配
*/
Arrays.stream(Introspector.getBeanInfo(o.getClass()).getPropertyDescriptors()).forEach(op ->
/**
* 名称匹配上了之后进行属性填充
*/
if (sp.getName().equals(op.getName()) && !sp.getName().equals("class"))
try
/**
* 对我们新创建的 o 对象进行属性填充,填充数据的来源为 sources
*/
op.getWriteMethod().invoke(o, sp.getReadMethod().invoke(source));
catch (IllegalAccessException e)
e.printStackTrace();
catch (InvocationTargetException e)
e.printStackTrace();
);
catch (IntrospectionException e)
e.printStackTrace();
);
/**
* o 对象属性填充完毕,添加至 targets集合中
*/
targets.add(o);
catch (IntrospectionException e)
e.printStackTrace();
catch (InstantiationException e)
e.printStackTrace();
catch (IllegalAccessException e)
e.printStackTrace();
);
@Data
static class ZzhSource
String name;
String age;
String sex;
@Data
static class ZzhTarget
String name;
String age;
String sex;
效果展示
可以看到数据成功的拷贝到了我们指定的 List 中了
正版 BeanUtils.copyProperties() 源码剖析
步骤流程总是惊人的相似,获取对象的 PropertyDescriptor ,然后调用内省为我们提供的各种操作 字段 属性的Api,最终达到属性拷贝的目的。
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable, @Nullable String... ignoreProperties) throws BeansException
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
//获取目标对象Class
Class<?> actualEditable = target.getClass();
if (editable != null)
if (!editable.isInstance(target))
throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]");
actualEditable = editable;
//获取目标对象的所有 PropertyDescriptor
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
PropertyDescriptor[] var7 = targetPds;
int var8 = targetPds.length;
//遍历目标对象的 PropertyDescriptor
for(int var9 = 0; var9 < var8; ++var9)
PropertyDescriptor targetPd = var7[var9];
//获取目标对象的写方法,通过此方法可以对指定字段赋值
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName())))
//获取源对象 PropertyDescriptor
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null)
//获取源对象的读方法,通过此方法可以获取对应字段的 value值
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()))
try
//设置一下方法操作权限,为可操作
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers()))
readMethod.setAccessible(true);
//获取源对象字段中的 value值
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers()))
writeMethod.setAccessible(true);
//为目标对象进行属性填充
writeMethod.invoke(target, value);
catch (Throwable var15)
throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15);
总结
Spring为我们提供的 BeanUtils 工具类其实也就是对 内省 Api中的一些方法的包转而已,思考:内省的实现原理是什么呢?我们是否可以从 Jdk 或者 CgLib动态代理入手,自己写一套 山寨版 内省 Api呢?答案肯定是可以的,以后有时间再去研究一下这个,读者感兴趣的话,也可以尝试着自己写一套内省 Api哦
以上是关于Spring提供的BeanUtils源码剖析(附手写copyProperties方法)的主要内容,如果未能解决你的问题,请参考以下文章
Spring提供的BeanUtils源码剖析(附手写copyProperties方法)