当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再次认识的一个很好的例子.
posted on 2018-06-22 16:12 mumuxinfei 阅读(2281) 评论(0) 编辑 收藏 举报