前言:
  BeanUtils(spring版/apache版)工具极大方便了java developer, 尤其在写业务代码中, 各种域模型DO, BO, VO等对象之间的复制. 但使用BeanUtils过程中, 也有些细节需要注意, 避免遇到一些神坑. 比如使用BeanUtils时最容易犯的错, 复制对象采用的是浅拷贝模式, 而并非预想的深拷贝模式.
  本文将讲解BeanUtils在遇到泛型时, 需要注意的一些问题.

复制特点:
  BeanUtils在复制(copyProperties)对象过程中, 除了开头提到过的浅拷贝模式外, 还具有以下一些特点.
  1. 成员存在性不一致
  source对象有, 但是dest对象没有, 这些成员属性直接忽略
  source对象没有, 但是dest对象有, 则dest的成员属性选用默认值.
  2. 名称和类型强匹配
  只有当source和dest的成员, 其名称和类型完全匹配时, 才进行复制. 唯一的例外, 是String和Date类型, BeanUtils有默认内置的Convertor允许互转(比较特殊).

场景模拟:
  让我们回到主题, 既然谈到了泛型, 那么我们来模拟一个案例, 来看看到底发生了什么?

@Setter
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
class Req<T> {
String name;
T value;
} @Setter
@Getter
@AllArgsConstructor
class Hello {
String key;
} @Setter
@Getter
@AllArgsConstructor
class World {
String key;
} public class TestCase { @Test
public void test() {
// t1为源对象
Req<Hello> t1 = new Req<Hello>("lilei", new Hello("key"));
// t2为目标对象
Req<World> t2 = new Req<World>(); // 借助spring的BeanUtils来复制对象, source->dest
BeanUtils.copyProperties(t1, t2); // 打印t2对象的内容
System.out.println(t2); // t2的value值预期为null
Assert.assertEquals(t2.getValue(), null);
} }

  执行的结果如下所示:

Req(name=lilei, value=com.test.Hello@4c178a76)

java.lang.AssertionError:
Expected :com.test.Hello@4c178a76
Actual :null

  和预期完全相反, 从打印对象t2中, 我们惊奇的发现, t2(类型为Req<World>)对象的成员value(World类型)竟然变成了Hello类型. 当使用t2对象的value成员时, 会在运行期遇到cast class的异常, 非常的诡异.
  不是说好, BeanUtils在复制对象时, 严格执行名称和类型强匹配的原则吗? 这是光天化日之下的打脸, ^_^.

分析和解决:
  一方面, 这个现象应该和java泛型的特殊性有关系, java泛型在编译时存在, 但是在编译后的字节码中就不复存在了, 或者说其在运行期其泛型类型已被擦拭掉了. 因此Req<Hello>和Req<World>在运行期内被统一视为Req<Object>类型, 所以t1对象的Hello类型value被赋予给了t2对象World类型的value.
  另一方面, BeanUtils.copyProperties其是基于反射来实现对象成员的复制的, 因此回避掉了编译期的检查.
  综上所述, 上篇代码的执行结果就可以合理和解释了.
  那如何解决这个问题呢? 可以针对泛型成员单独复制来解决该问题.

public class TestCase {

    @Test
public void test() {
// t1为源对象
Req<Hello> t1 = new Req<Hello>("lilei", new Hello("key"));
// t2为目标对象
Req<World> t2 = new Req<World>(); // 借助spring的BeanUtils来复制对象, source->dest
BeanUtils.copyProperties(t1, t2);
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// 既解决深拷贝问题, 又纠正泛型问题
if ( t1.getValue() != null ) {
t2.setValue(new World(""));
BeanUtils.copyProperties(t1.getValue(), t2.getValue());
}
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // 打印t2对象的内容
System.out.println(t2); // t2的value值预期为null
Assert.assertEquals(t2.getValue().getKey(), "key");
} }

  注:  "++++++++++++++++++++++"串包围的代码段尝试去纠正了这个问题, 测试也可以.

  测试结果如下:

Req(name=lilei, value=com.test.World@fa4c865)

  

总结:
  这个问题场景, 也是实际开发中遇到, 也算是对java泛型和BeanUtils再次认识的一个很好的例子.

当BeanUtils遇到泛型的更多相关文章

  1. JavaBean 内省API BeanUtils工具 泛型 xml xml约束

    1 什么是JavaBean?有何特征? 1)符合特定规则的类    2)JavaBean分二类:     a)侠义的JavaBean         .私有的字段(Field)         .对私 ...

  2. 基于表单数据的封装,泛型,反射以及使用BeanUtils进行处理

    在Java Web开发过程中,会遇到很多的表单数据的提交和对表单数据的处理.而每次都需要对这些数据的字段进行一个一个的处理就显得尤为繁琐,在Java语言中,面向对象的存在目的便是为了消除重复代码,减少 ...

  3. 一个好用的hibernate泛型dao

    以前从springside2.0上搞下来的很好用的,基本实现dao零编码只要配置xml文件就行了. 先看图: 一共4层,com.demonstration.hibernate.basedao是我加的用 ...

  4. java 反射,注解,泛型,内省(高级知识点)

     Java反射 1.Java反射是Java被视为动态(或准动态)语言的一个关键性质.这个机制允许程序在运行时透过Reflection APIs    取得任何一个已知名称的class的内部信息, 包括 ...

  5. BeanUtils在web项目中的应用

    package cn.gdpe.jdbc; import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; im ...

  6. BeanUtils制作自定义的转换器

    一般来说,BeanUtils自带的Converter基本上可以满足我们在开发过程中的使用了,然而很多时候我们还是需要自定义一些转换器. MyBean.java package beanutils; i ...

  7. Java基础---Java---基础加强---内省的简单运用、注解的定义与反射调用、 自定义注解及其应用、泛型及泛型的高级应用、泛型集合的综合

    内省的简单运用: JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则. 采用遍历BeanInfo的所有属性方式来查找和 ...

  8. Java下的框架编程(反射,泛型,元数据,CGLib,代码动态生成,AOP,动态语言嵌入)

    Java 虽然没有动态语言般暴起,但仍然天连天,水接水的生出好多框架技术---反射(reflection),泛型(generics),元数据(annotation),proxies(proxy/cgl ...

  9. BeanUtils简化数据封装

    BeanUtils主要用来封装JavaBean的. 1.什么是JavaBean JavaBean指的是标准的类. 要求: 1. 类必须被public修饰2. 必须提供空参的构造器3. 成员变量必须使用 ...

随机推荐

  1. rpc框架实现(持续更新)

    网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,rpc基于长连接的远程过程调用应用而生. 一:A服务调用B服务,整个调用过程,主要经历如下几个步骤:(摘自 ...

  2. Jmeter的使用简介及实例

    一.安装及配置环境1.安装   java环境   该软件需要java环境,安装jdk,在百度自行查找安装   环境变量配置:变量名JAVA_HOME 值:jdk的安装路径                ...

  3. 接口测试--postman简介

    一.什么是接口测试 接口测试是测试系统组件间接口的一种测试.接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点.测试的重点是要检查数据的交换,传递和控制管理过程,以及系统间的相互逻辑 ...

  4. Units about ASM

    1.ASM Striping and Mirroring:ASM supports two levels of striping: fine striping and coarse striping. ...

  5. 通过配置hosts.allow和hosts.deny文件允许或禁止ssh或telnet操作

    1.登录主机,如果是普通账户先切换至root账号 su root 2.编缉/etc/hosts.allow文件 vi /etc/hosts.allow 允许内容 书写格式(改成自自需要的IP或IP段) ...

  6. Qt动态布局

    QVBoxLayout *m_pvLayout = NULL: QWidget *m_pWidgetPlay = NULL: m_pvLayout = new QVBoxLayout(this); m ...

  7. chrome hosts

    # Go Hosts# 2017-05-02 02:13:04# Localhost (DO NOT REMOVE)127.0.0.1    localhost::1    localhost ip6 ...

  8. 003-RHEL7-Linux系统维护管理命令使用

    系统维护管理命令: date  查看日期,设置日期 只有超级用户才能用date命令设置时间 date  --help  显示时间的帮助命令 date{选项} 显示时间格式(以+开头,后面接时间格式) ...

  9. Buffer与Cache区别 简要说明

    Buffer –  缓冲区 写 用户写入数据存储区域 解决写入冲突           CPU-Memoury-Disk Cache – 缓存区 读 用户读取缓存数据使用 临时存储 Disk-memo ...

  10. NAND Flash vs NOR Flash

    Avinash Aravindan reference:https://www.embedded.com/design/prototyping-and-development/4460910/2/Fl ...