当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. 成员变量必须使用 ...
随机推荐
- mac navicate破解版汉化
https://pan.baidu.com/s/1dRoalG8lZ-AMGmZrj8OhpQ 提取密码:e8ad 安装完navicate之后解压zh-Hans.zip 点击Resources文件夹 ...
- laravel使用使用 Php Artisan Tinker 实现模型的增删改查
tinker命令: php artisan tinker 查阅数据库数据: App\User::count(); App\User::where('username', 'samuel')->f ...
- laravel中文件上传:
laravel5.5版本: congfig下的filesystems.php中配置:uploads信息: 'uploads' => [ 'driver' => 'local', 'root ...
- AdaBoost, LogitBoost and GradientBoosting
前向分步算法与加法模型(forward stagewise algorithm and additive model) (1) AdaBoost:前向分步算法中损失函数取指数损失函数 (2) Logi ...
- shell IF分支判断语句
单分支IF条件语句 if [ 条件判断式 ] then 程序: fi //结束的时候if反过来写 fi ----------------------------- /** * if test -d ...
- html页面标题增加图标方法
有些网站的网页标题部分有图标,很带感.方法很简单: 在<head></head>部分增加下列一行代码即可. <link rel="shortcut icon&q ...
- 【阅读笔记】《C程序员 从校园到职场》第三章 程序的样式(大括号)
参考: https://blog.csdn.net/zhouzhaoxiong1227/article/details/22820533 一..初始化数组变量 在实际的软件开发项目中,变量在使用前应初 ...
- 數據庫ORACLE轉MYSQL存儲過程遇到的坑~(總結)
ORACLE數據庫轉MySQL數據庫遇到的坑 總結 最近在做Oracle轉mysql的工程,遇到的坑是真的多,尤其是存儲過程,以前都沒接觸過類似的知識,最近也差不多轉完了就總結一下.希望能幫到一些人( ...
- Jenkins持续集成web项目(七)
功能:用户开发完maven构建的web项目后,从本地提交代码到gogs远程仓库中,在执行 git commit 命令之前会先执行 maven配置的 findbugs插件,来检测项目是否有明显bug,如 ...
- C++的string类型和继承C语言风格的字符串的区别与注意事项
1.尽可能地在C++程序中使用string,不要使用继承而来的C语言风格的字符串,会出现许多安全问题. 2.C语言的字符串风格,是以空字符结束的,在C++的头文件cstring中定义了C语言风格的字符 ...