前言:
  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. Apache支持TRACE请求漏洞处理方案

    trace和get一样是http的一种请求方法,该方法的作用是回显收到的客户端请求,一般用于测试服务器运行状态是否正常. 该方法结合浏览器漏洞可能造成跨站脚本攻击.修复方法如下: 编缉/etc/htt ...

  2. c++ 软件版本比较函数

    // 版本号拆分为数组 void splitToInt(string str , vector<int> *v1, char delim ){ // 拆分 string strTmp; s ...

  3. Spring事务的开启方式

    1.通过注解方式@Transactional @Transactional(rollbackForClassName = { "Exception", "RuntimeE ...

  4. forget sus,syn sym semi word out~s

    1★ sus 在~下面     2★ syn 3★ sym 共同   4★ semi   半  

  5. 各种形式的熵函数,KL距离

    自信息量I(x)=-log(p(x)),其他依次类推. 离散变量x的熵H(x)=E(I(x))=-$\sum\limits_{x}{p(x)lnp(x)}$ 连续变量x的微分熵H(x)=E(I(x)) ...

  6. python-面向对象增强版

    class Person: def __init__(self, name, id, gender, birth): self.name = name # 实例变量 对象里的变量 self.id = ...

  7. what’s this?

    jdk,jre,jvm三者区别:JDK: (Java Development ToolKit) java开发工具包.JDK是整个java的核心! 包括了java运行环境 JRE(Java Runtim ...

  8. git-github-TortoiseGit综合使用教程(二)快速入门

    :建立版本库 在github网站上创建一个版本库,并复制clone地址. git@github.com:jackadam1981/Flask_Base.git https://github.com/j ...

  9. Linux文件系统中的inode节点详细介绍

    这篇文章主要介绍了Linux文件系统中的inode节点,详细讲解了inode是什么.inode包含的信息.inode号码的相关资料等,需要的朋友可以参考下 一.inode是什么? 理解inode,要从 ...

  10. 4.1 C++多态的概念及前提条件

    参考:http://www.weixueyuan.net/view/6370.html 总结: 而多态的功能则是将函数名动态绑定到函数入口地址,这样的动态绑定过程称为运行期绑定. 而在运行期绑定的函数 ...