zoukankan      html  css  js  c++  java
  • 当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再次认识的一个很好的例子.

  • 相关阅读:
    HTTP方法(转)(学习基础)
    正则表达式 学习手记 111221
    原型模式 学习手记
    分布式事务 MSDTC配置
    Ibatis.Net 学习手记二 缓存
    IIS 7.0 部署MVC
    事务与分布式事务
    Ibatis+MVC 3.0 开发手记
    Ibatis.Net 学习手记一 简单的Demo
    简单工厂 学习手记
  • 原文地址:https://www.cnblogs.com/mumuxinfei/p/9213163.html
Copyright © 2011-2022 走看看