Ref
- 关于BeanUtils.copyProperties的用法和优缺点 | 华为云
- ref-Spring的BeanUtils采坑| CSDN
- BeanUtils引入不同的包的结果(坑) | CSDN
- ref-BeanUtils性能测试-1
- ref-BeanUtils性能测试-2
Java对象——PO,VO,DAO,BO,POJO
对象 | 释意 | 使用 | 备注 |
---|---|---|---|
PO(persistant object) | 持久对象 | 可以看成是与数据库中的表相映射的Java对象,最简单的PO就是对应数据库中某个表中的一条记录。 | PO中应该不包含任何对数据库的操作 |
VO(view object) | 表现层对象 | 主要对应界面显示的数据对象。 | 对于一个WEB页面,或者SWT、SWING的一个界面,用一个VO对象对应整个界面的值。 |
BO(business object) | 业务对象 | 封装业务逻辑的 Java 对象 | 通过调用DAO方法,结合PO,VO进行业务操作。 |
POJO(plain ordinary java object) | 简单无规则Java对象 | POJO和其他对象不是一个层面上的对象划分,VO和PO应该都属于POJO | POJO是最多变的对象,是一个中间对象。一个POJO持久化以后就是PO,直接用它传递、传递过程中就是DTO,直接用来对应表示层就是VO |
DAO(data access object) | 数据访问对象 | 此对象用于访问数据库。 | 通常和PO结合使用,DAO中包含了各种数据库的操作方法。通过它的方法,结合PO对数据库进行相关的操作。 |
DTO (data transfer object) | 数据传输对象 | 主要用于远程调用等需要大量传输对象的地方。 | 比如我们一张表有100个字段,那么对应的PO就有100个属性。但是界面上只要显示10个字段,客户端用WEB service 来获取数据,没有必要把整个PO对象传递到客户端。这时就可以用只有这 10 个属性的 DTO 来传递结果到客户端,这样也不会暴露服务端表结构。到达客户端以后,如果用这个对象来对应界面显示,那此时它的身份就转为VO |
Overview
在开发中,常使用 BeanUtils.copyProperties()
进行PO,VO,DTO等对象的复制和转换。
BeanUtils 提供对 Java 反射和自省 API 的包装。其主要目的是利用反射机制对 Java Bean 的属性进行处理,在这里只介绍它的 copyProperties()
方法。
需要注意的是
- 使用 Spring 的
BeanUtils # CopyProperties
方法去拷贝对象属性时,需要对应的属性有getter
和setter
方法(内部实现时,使用反射拿到set
和get
方法,再去获取/设置属性值); - 如果存在属性完全相同得内部类,但不是同一个内部类(即分别属于各自得内部类),则 Spring 会认为属性不同,不会Copy;
- 泛型只在编译期起作用,不能依靠泛型来做运行期的限制;
- Spring 和 Apache 的
copy
属性得方法源和目的参数得位置正好相反,所以导包和调用得时候需要注意。
Method
// Apache 将源对象中的值拷贝到目标对象
// 注意,目标对象在前
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
如果你有两个具有很多相同属性的 Java Bean,一个很常见的情况就是 Struts 里的 PO 对象(持久对象)和对应的 ActionForm。例如 Teacher 和 TeacherForm。我们一般会在 Action 里从 ActionForm 构造一个 PO 对象,传统的方式是使用类似下面的语句对属性逐个赋值。
//得到TeacherForm
TeacherForm teacherForm=(TeacherForm)form;
//构造Teacher对象
Teacher teacher=new Teacher();
//赋值
teacher.setName(teacherForm.getName());
teacher.setAge(teacherForm.getAge());
teacher.setGender(teacherForm.getGender());
teacher.setMajor(teacherForm.getMajor());
teacher.setDepartment(teacherForm.getDepartment());
//持久化Teacher对象到数据库
HibernateDAO.save(teacher);
而使用 BeanUtils 后,代码就大大改观了,如下所示
//得到TeacherForm
TeacherForm teacherForm=(TeacherForm)form;
//构造Teacher对象
Teacher teacher = new Teacher();
//赋值
BeanUtils.copyProperties(teacher,teacherForm);
//持久化Teacher对象到数据库
HibernateDAO.save(teacher);
如果 Teacher 和 TeacherForm 间存在名称不相同的属性,则 BeanUtils 不对这些属性进行处理,需要程序员手动处理。例如 Teacher 包含 modifyDate(该属性记录最后修改日期,不需要用户在界面中输入)属性,而 TeacherForm 无此属性,那么在上面代码的 copyProperties() 后还要加上一句
teacher.setModifyDate(new Date());
除 BeanUtils 外,还有一个名为 PropertyUtils 的工具类,它也提供 copyProperties()
方法,作用与 BeanUtils 的同名方法十分相似,主要的区别在于后者提供类型转换功能,即发现两个 Java Bean的同名属性为不同类型时,在支持的数据类型范围内进行转换,而前者不支持这个功能,但是速度会更快一些。BeanUtils 支持的转换类型如下
- java.lang.BigDecimal
- java.lang.BigInteger
- boolean and java.lang.Boolean
- byte and java.lang.Byte
- char and java.lang.Character
- java.lang.Class
- double and java.lang.Double
- float and java.lang.Float
- int and java.lang.Integer
- long and java.lang.Long
- short and java.lang.Short
- java.lang.String
- java.sql.Date
- java.sql.Time
- java.sql.Timestamp
这里要注意一点,java.util.Date
是不被支持的,而它的子类 java.sql.Date
是被支持的。因此如果对象包含时间类型的属性,且希望被转换的时候,一定要使用 java.sql.Date
类型。否则在转换时会提示 argument mistype
异常。
优缺点
// Apache 将源对象中的值拷贝到目标对象
// 注意,目标对象在前
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
BeanUtils.copyProperties(destObject, origObjecr)
上述方法的优点是可以简化代码。
缺点是使用 BeanUtils 的成本惊人地昂贵!BeanUtils 所花费的时间要超过取数据、将其复制到对应的 destObject 对象(通过手动调用 get 和 set 方法),以及通过串行化将其返回到远程的客户机的时间总和。所以要小心使用这种威力!
采坑
在使用时,需要注意如下几点
- 使用 Spring 的
BeanUtils # CopyProperties
方法去拷贝对象属性时,需要对应的属性有getter
和setter
方法(内部实现时,使用反射拿到set
和get
方法,再去获取/设置属性值); - 如果存在属性完全相同得内部类,但不是同一个内部类(即分别属于各自得内部类),则 Spring 会认为属性不同,不会Copy;
- 泛型只在编译期起作用,不能依靠泛型来做运行期的限制;
- Spring 和 Apache 的
copy
属性得方法源和目的参数得位置正好相反,所以导包和调用得时候需要注意。
内部类的拷贝
如果存在属性完全相同得内部类,但不是同一个内部类(即分别属于各自得内部类),则 Spring 会认为属性不同,不会Copy。
下面给出例子进行验证。
@Data
public class TestEntity{
private Integer age;
private String name;
private Inner inner;
@Data
public static class Inner{
private Integer a;
public Inner(Integer a){
this.a = a;
}
}
}
@Data
public class TestVO{
private Integer age;
private String name;
private Inner inner;
@Data
public static class Inner{
private Integer a;
public Inner(Integer a){
this.a = a;
}
}
}
public class Main{
public static void main(String args[]){
TestEntity entity = new TestEntity();
entity.setAge(1);
entity.setName("hehe");
entity.setInner(new TestEntity.Inner(1));
TestVO vo = new TestVO();
BeanUtils.copyProperties(entity,vo);
System.out.println(vo.toString());
}
}
执行代码,程序输入如下。可以看到,对象的 inner
是空的。
TestVO(age=1, name=lbs, inner=null)
查看编译后的 .class
文件。可以看到,TestEntity.java
和 TestVO.java
里面的 Inner
在编译之后的 class
名字不一样(代表加载到虚拟机之后的地址不同),因此无法拷贝成功。
// 编译后的 .class 文件
TestEntity$Inner.class
TestEntity.class
TestVO$Inner.class
TestVO.class
那么问题来了哈,我们怎样用才能让其拷贝成功呢?将代码修改如下。
@Data
public class TestVO{
private Integer age;
private String name;
private TestEntity.Inner inner;
}
仅仅是把 Inner
变为了 TestEntity.Inner
,删掉了没引用得内部类 Inner
,再次执行测试代码,然后运行结果如下
TestVO(age=1, name=lbs, inner=TestEntity.Inner(a=1))
可以看到, TestEntity
对象里面的 inner
被成功拷贝过来。此时编译后的 class
文件也由4个变为了3个。
// 编译后的 .class 文件
TestEntity$Inner.class
TestEntity.class
TestVO.class
Spring 和 Apache 的 copyProperties
Apache中,org.apache.commons.beanutils.BeanUtils # copyProperties
源码如下。
// Apache 将源对象中的值拷贝到目标对象
// 注意,目标对象在前
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
默认情况下,使用 org.apache.commons.beanutils.BeanUtils
对复杂对象的复制是引用,这是一种浅拷贝。
由于 Apache 下的 BeanUtils 对象拷贝性能太差,不建议使用,而且在阿里巴巴 Java 开发规约插件上也明确指出
Ali-Check | 避免用Apache Beanutils进行属性的copy。
commons-beantutils
对于对象拷贝加了很多的检验,包括类型的转换,甚至还会检验对象所属的类的可访问性,可谓相当复杂,这也造就了它的差劲的性能,具体实现代码如下。
public void copyProperties(final Object dest, final Object orig)
throws IllegalAccessException, InvocationTargetException {
// Validate existence of the specified beans
if (dest == null) {
throw new IllegalArgumentException
("No destination bean specified");
}
if (orig == null) {
throw new IllegalArgumentException("No origin bean specified");
}
if (log.isDebugEnabled()) {
log.debug("BeanUtils.copyProperties(" + dest + ", " +
orig + ")");
}
// Copy the properties, converting as necessary
if (orig instanceof DynaBean) {
final DynaProperty[] origDescriptors =
((DynaBean) orig).getDynaClass().getDynaProperties();
for (DynaProperty origDescriptor : origDescriptors) {
final String name = origDescriptor.getName();
// Need to check isReadable() for WrapDynaBean
// (see Jira issue# BEANUTILS-61)
if (getPropertyUtils().isReadable(orig, name) &&
getPropertyUtils().isWriteable(dest, name)) {
final Object value = ((DynaBean) orig).get(name);
copyProperty(dest, name, value);
}
}
} else if (orig instanceof Map) {
@SuppressWarnings("unchecked")
final
// Map properties are always of type <String, Object>
Map<String, Object> propMap = (Map<String, Object>) orig;
for (final Map.Entry<String, Object> entry : propMap.entrySet()) {
final String name = entry.getKey();
if (getPropertyUtils().isWriteable(dest, name)) {
copyProperty(dest, name, entry.getValue());
}
}
} else /* if (orig is a standard JavaBean) */ {
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
}
}
}
}
}
而 Spring 的 copyProperties
的使用如下。
package org.springframework.beans;
// Spring 将源对象中的值拷贝到目标对象
// 注意,目标对象在后。
public static void copyProperties(Object source, Object target) throws BeansException {
copyProperties(source, target, null, (String[]) null);
}
对比 Spring 和 Apache 的 copyProperties
方法,可以发现两者参数顺序不一样,在使用时一定要注意这个区别。
package org.apache.commons.beanutils.BeanUtils;
// Apache 将源对象中的值拷贝到目标对象
// 注意,目标对象在前
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {
BeanUtilsBean.getInstance().copyProperties(dest, orig);
}
类别 | 所在的包 | 函数参数 | 性能 |
---|---|---|---|
Apache | org.apache.commons.beanutils.BeanUtils | copyProperties(Object dest, Object orig) | 较差 |
Spring | org.springframework.beans | copyProperties(Object source, Object target) | 较好 |
Spring 下的 BeanUtils # copyProperties
方法实现比较简单,就是对两个对象中相同名字的属性进行简单的 get/set
,仅检查属性的可访问性,因此具有较好的性能,优于 Apache 的 copyProperties
。具体实现如下。
//Spring 中 copyProperties
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<?> 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[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
可以看到,成员变量赋值是基于目标对象的成员列表,但必须保证同名的两个成员变量类型相同。
包装类型
在进行属性拷贝时,低版本的 Apache CommonsBeanUtils 为了解决 Date 为空的问题,会导致为目标对象的原始类型的包装类属性赋予初始值。如 Integer 属性默认赋值为 0,尽管你的来源对象该字段的值为 null。
这个在我们的包装类属性为 null 值时有特殊含义的场景,非常容易踩坑!例如搜索条件对象,一般 null 值表示该字段不做限制,而 0 表示该字段的值必须为0。
改用其他工具时
如上一章节 「Spring 和 Apache 的 copyProperties」所讲,知道了 Apache CommonsBeanUtils 的性能较差后,要改用 Spring 的 BeanUtils 时,需要注意两者的参数顺序问题。记得将 targetObject
和 sourceObject
两个参数顺序对调。
BeanUtils性能测试
参见上述参考链接1的测试结果,Cglib
的 BeanCopier
的拷贝速度是最快的,即使是百万次的拷贝也只需要 10 毫秒! 相比而言,最差的是 Apache Commons 包的 BeanUtils.copyProperties
方法,100 次拷贝测试与表现最好的 Cglib 相差 400 倍之多。百万次拷贝更是出现了 2600 倍的性能差异!
实现 | 100 | 1000 | 10000 | 100000 | 1000000 |
---|---|---|---|---|---|
StaticCglibBeanCopier | 0.053561 | 0.680016 | 1.196787 | 2.924973 | 10.769302 |
CglibBeanCopier | 4.099259 | 12.252336 | 33.50937 | 48.940261 | 104.005539 |
SpringBeanUtils | 3.80229 | 9.268228 | 26.635362 | 118.699586 | 162.996875 |
CommonPropertyUtils | 6.7971116 | 20.59255 | 49.380707 | 219.271803 | 1857.382452 |
CommonBeanUtils | 23.566713 | 106.971358 | 473.95897 | 2619.76277 | 26199.132175 |
今天的文章BeanUtils.copyProperties使用和性能分析分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/18167.html