当BeanUtils遇到泛型
前言:
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遇到泛型的更多相关文章
- JavaBean 内省API BeanUtils工具 泛型 xml xml约束
1 什么是JavaBean?有何特征? 1)符合特定规则的类 2)JavaBean分二类: a)侠义的JavaBean .私有的字段(Field) .对私 ...
- 基于表单数据的封装,泛型,反射以及使用BeanUtils进行处理
在Java Web开发过程中,会遇到很多的表单数据的提交和对表单数据的处理.而每次都需要对这些数据的字段进行一个一个的处理就显得尤为繁琐,在Java语言中,面向对象的存在目的便是为了消除重复代码,减少 ...
- 一个好用的hibernate泛型dao
以前从springside2.0上搞下来的很好用的,基本实现dao零编码只要配置xml文件就行了. 先看图: 一共4层,com.demonstration.hibernate.basedao是我加的用 ...
- java 反射,注解,泛型,内省(高级知识点)
Java反射 1.Java反射是Java被视为动态(或准动态)语言的一个关键性质.这个机制允许程序在运行时透过Reflection APIs 取得任何一个已知名称的class的内部信息, 包括 ...
- BeanUtils在web项目中的应用
package cn.gdpe.jdbc; import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; im ...
- BeanUtils制作自定义的转换器
一般来说,BeanUtils自带的Converter基本上可以满足我们在开发过程中的使用了,然而很多时候我们还是需要自定义一些转换器. MyBean.java package beanutils; i ...
- Java基础---Java---基础加强---内省的简单运用、注解的定义与反射调用、 自定义注解及其应用、泛型及泛型的高级应用、泛型集合的综合
内省的简单运用: JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则. 采用遍历BeanInfo的所有属性方式来查找和 ...
- Java下的框架编程(反射,泛型,元数据,CGLib,代码动态生成,AOP,动态语言嵌入)
Java 虽然没有动态语言般暴起,但仍然天连天,水接水的生出好多框架技术---反射(reflection),泛型(generics),元数据(annotation),proxies(proxy/cgl ...
- BeanUtils简化数据封装
BeanUtils主要用来封装JavaBean的. 1.什么是JavaBean JavaBean指的是标准的类. 要求: 1. 类必须被public修饰2. 必须提供空参的构造器3. 成员变量必须使用 ...
随机推荐
- Lexicography
An anagram of a string is any string that can be formed using the same letters as the original. (We ...
- leetcode Most Common Word——就是在考察自己实现split
819. Most Common Word Given a paragraph and a list of banned words, return the most frequent word th ...
- Apache+Tomcat+mod_jk配置教程
0.说明 首先我们要弄明白mod_jk的作用是反向代理,而其实使用httpd.conf中的<VirtualHost>标签就可以实现反向代理,为什么还要多搞个mod_jk那么麻烦做反向代理. ...
- UNIX-like系统资源检查命令
系统资源主要是内存.磁盘.CPU三项,其中任一项资源用尽都会造成系统崩溃. 系统 内存 磁盘空间 磁盘io CPU Linux free -g df -h top AIX svmon -G/vms ...
- Java文档注释导出帮助文档和项目的jar包导入和导出。
1.1 文档注释导出帮助文档 在eclipse使用时,可以配合文档注释,导出对类的说明文档,从而供其他人阅读学习与使用. 通过使用文档注释,将类或者方法进行注释用@简单标注基本信息.如@au ...
- LY.JAVA.DAY12.String类
2018-07-24 14:06:03 String类概述 字符串是由多个字符组成一串数据(字符序列) 字符串可以看成字符数组 一旦被赋值就不能被改变 值不能变 1.过程概述: 方法区---字符 ...
- UVa 3602 - DNA Consensus String 水题 难度: 0
题目 https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_pr ...
- 【IAP支付之三】苹果IAP安全支付与防范 receipt收据验证
这里网上的朋友已经介绍的很详细了,具体的链接已经无法找到了. 这里主要说几点本人在开发中遇到的问题: 1.漏单必须要处理,玩家花RMB购买的东西却丢失了,是绝对不能容忍的.所谓的漏单就是玩家已经正常付 ...
- Python Django 之 静态文件存放设置
一.静态文件存放路径设置STATICFILES_DIRS 1.在django项目目录下面新建静态文件保存目录 2.在setting中添加相应寻找静态文件目录的配置 STATICFILES_DIRS=( ...
- OPENWRT安装配置指南之 17.01.4 LEDE
简介 这个东西,需要看简介的就不要看下去了. 下面已刚刷进去,路由IP地址为192.168.1.1为例开始配置. 浏览器访问192.168.1.1,无密码. 一:配置上网 不管你是什么方式上网,请根据 ...