怕什么真理无穷,进一步有近一步的欢喜
对项目中经常使用的属性拷贝工具类进行总结:
org.apache.commons.beanutils.BeanUtilsorg.apache.commons.beanutils.PropertyUtilsorg.springframework.beans.BeanUtils首先明确下在Java中字段和属性的区别。 属性是不是类里最上边的那些全局变量吗?比如:
public class UserTest{ private String userName; private String password; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getHello() { return "hello"; } public void setHello(String str) { } }上面 private String userName;private String password;。准确的来说它们应该称为:字段,而不是本次要讲的属性。 下面简述一下:什么是Java中的属性?
Java中的属性(property),通常可以理解为get和set方法,而字段(field),通常叫做“类成员”,或“类成员变量”,有时也叫“域”,理解为“数据成员”,用来承载数据的。
直白点就是Java中的属性是指:设置和读取字段的方法,也就是平常见到的set和get方法。只要是set和get开头的方法在Java里都认为它是属性(请注意这句话,等下后边会写代码做验证) 属性名称:就是set和get方法名 去掉"set"和"get"后的内容 比如:
public void setUserName(String userName) { this.userName = userName; }它的属性名称是:userName(也就是方法名称”setUserName”去掉“set”) 当然 setUserName和 getUserName 方法是指同一个属性 UserName, 这里再次提醒:字段和属性不是同一个东西。 代码验证属性上面代码中还有一个 getHello 和 setHello , JDK 中有个API Introspector
获取的是java.beans.BeanInfo 类。这个类可以通过
java.beans.BeanInfo#getPropertyDescriptors : 获取java bean 所有的属性。
public static void main(String[] args) throws IntrospectionException { BeanInfo beanInfo = Introspector.getBeanInfo(UserTest.class); // 得到类中的所有的属性描述器 PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); System.out.println("属性的个数:" + pds.length); for (PropertyDescriptor pd : pds) { System.out.println("属性:" + pd.getName()); } }结果:
属性的个数:4 属性:class 属性:hello 属性:password 属性:userName上面多了一个 class ,原因很简单,因为Object类是所有类的父类,Object类里有个方法叫 getClass();所以这也验证了咱们刚才说的: “只要是set或者get开头的方法都叫属性”
default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。 public : 对所有类可见。使用对象:类、接口、变量、方法 private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类) protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
1.基本类型和包装类型会自动转换, 方法名称相同,返回值类型和参数类型不同,不进行复制,也不报错_ 2.支持指定忽略某些属性不复制 3、支持类的修饰符 default 、 public
1.基本类型和包装类型会自动转换 2.方法名称相同,返回值类型和参数类型不同,复制失败,会报错,如下:
_argument type mismatch - had objects of type “java.lang.Double” but expected signature “java.lang.String”
3.只支持类的修饰符 public,如果是default 则直接不会进行转换(注意内部类复制也要加public)
1.基本类型和包装类型会自动转换 2.方法名称相同,返回值类型和_ _参数类型不同,不复制,不报错 3.只支持类的修饰符 public,如果是default 则直接不会进行转换(注意内部类复制也要加public)
tips: Spring和apache的_copyProperties_属性的方法源和目的参数的位置正好相反,所以导包和调用的时候都要注意一下。
// Apache public static void copyProperties(final Object dest, final Object orig) // Spring public static void copyProperties(Object source, Object target)性能参考: Bean复制的几种框架性能比较(Apache BeanUtils、PropertyUtils,Spring BeanUtils,Cglib BeanCopier)
摘要总结:Spring是在次数增多的情况下,性能较好,在数据较少的时候,性能比PropertyUtils的性能差一些。PropertyUtils的性能相对稳定,表现是呈现线性增长的趋势。而Apache的BeanUtil的性能最差,无论是单次Copy还是大数量的多次Copy性能都不是很好。
使用的压测工具备忘:Java使用JMH进行简单的基准测试Benchmark : http://irfen.me/java-jmh-simple-microbenchmark/
根据上面的具体的分析还是使用 :org.springframework.beans.BeanUtils#copyProperties原因: 1.这个方法在复制的时候不会因为属性的不同而报错,影响代码执行 2.性能方面也相对较好
其他Apache的两个,1、org.apache.commons.beanutils.PropertyUtils#copyProperties 复制会直接报错 2、org.apache.commons.beanutils.BeanUtils#copyProperties 性能相对较差
核心本质都是使用反射实现。具体的实现代码稍有不同。
1、获取 目标对象 所有的属性 targetPds
PropertyDescriptor_[] _targetPds = getPropertyDescriptors(actualEditable);
2、循环 targetPds ,并在源对象取出对应的属性
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName_())_;
3、r如果不是修饰不是public,**暴力反射 ,**然后使用对属性进行设值
setAccessible_(true);// 暴力反射 writeMethod.invoke(target, value)_;
### apache.commons#BeanUtils org.apache.commons.beanutils.BeanUtilsBean#copyProperties简单截取核心代码:
// org.apache.commons.beanutils.BeanUtilsBean#copyProperties final PropertyDescriptor[] origDescriptors = getPropertyUtils().getPropertyDescriptors(orig); for (PropertyDescriptor origDescriptor : origDescriptors) { final String name = origDescriptor.getName(); if ("class".equals(name)) { continue; // No point in trying to set an object's class } if (getPropertyUtils().isReadable(orig, name) && getPropertyUtils().isWriteable(dest, name)) { try { final Object value = getPropertyUtils().getSimpleProperty(orig, name); copyProperty(dest, name, value); } catch (final NoSuchMethodException e) { // Should not happen } } } // org.apache.commons.beanutils.BeanUtilsBean#copyProperty getPropertyUtils().setSimpleProperty(target, propName, value); // org.apache.commons.beanutils.PropertyUtilsBean#setSimpleProperty invokeMethod(writeMethod, bean, values);1、 获取的是源对象的所有的属性
final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);
2、如果属性是class,不复制
if (“class”.equals_(name)) { continue; // No point in trying to set an object’s class}_
3、循环源对象的属性,做一些检验
copyProperty(dest, name, value); 1、会检验目标对象是否有源对象的属性,没有跳过 2、获取属性的名称类型
4、然后给目标对象设置,最终还是使用反射
method.invoke_(bean, values)_;
简单截取核心代码:
// org.apache.commons.beanutils.PropertyUtilsBean#copyProperties final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig); for (PropertyDescriptor origDescriptor : origDescriptors) { final String name = origDescriptor.getName(); if (isReadable(orig, name) && isWriteable(dest, name)) { try { final Object value = getSimpleProperty(orig, name); if (dest instanceof DynaBean) { ((DynaBean) dest).set(name, value); } else { setSimpleProperty(dest, name, value); } } catch (final NoSuchMethodException e) { if (log.isDebugEnabled()) { log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e); } } } } // org.apache.commons.beanutils.PropertyUtilsBean#invokeMethod method.invoke(bean, values);1、 获取的是源对象的所有的属性
final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);
2、循环源对象的属性,然后给目标对象设置,最终还是使用反射
结合使用说明以及相关的性能和原理分析,建议使用 org.springframework.beans.BeanUtils#copyPropertie
https://www.cnblogs.com/kaka/archive/2013/03/06/2945514.html
Java反射——内省(Introspector)以及BeanUtils内省框架https://blog.csdn.net/ju_362204801/article/details/90672396 Java编程技术乐园:分享干货技术,每天进步一点点,小的积累,带来大的改变!扫描关注,后台回复【秘籍】,获取珍藏干货! 99.9%的伙伴都很喜欢