当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  阅读(2362)  评论(0编辑  收藏  举报

导航