JAVA中使用spring的浅克隆

场景 : 最近,应项目要求要拆分表(原先表字段过多),拆成多个表(A表分成B表跟C表,B表保留基本信息,C表保留详细信息),这个时候我们希望后台第一个修订版不作出过大改动,所以我们最实际的方法就先把A表干掉,使用D实体类代替(A的备份),但是D的数据需要从B+C拷贝过来。  或者说我们的场景就是比如 M实体类跟数据库对应,N是M的包装类,但是我们很多时候是直接把M的数据拷贝到N类中,所以我们可以使用N继承M,或者N拥有M的很多属性,然后我们使用浅克隆拷贝即可。

注意 : 我们使用的浅克隆是spring当中的BeanUtils的copyProperties方法,org.springframework.beans.BeanUtils.copyProperties(Object source,Object target)。

方法说明 : 我们可以传递三个参数,第一个param是源obj,第二个是目标obj,第三个是需要忽略的属性数组(即是我们不想拷贝的属性)。

实际测试 :  1. 测试下Date类型跟Set类型能够复制;   2. 测试下NULL值能否复制; 


 

@Test 
    public  void testCopyProperties() {
        // DXB2171127AF6100001 
        String sql  = "select * from in_applyinfo where status_lookup_code='140' and applyid='IST1180208AL2014102' allow filtering";
        ApplyInfoMigration applyInfoMigration = applyInfoDao.selectOne(sql,ApplyInfoMigration.class );   // A实体
        ApplyInfo appInfo = new ApplyInfo();         // B表
        ApplyInfoDetail applyInfoDetail = new ApplyInfoDetail();    // C表
        System.out.println("-------------------> 属性开始copy-------》");
        // BeanUtils.copyProperties
        BeanUtils.copyProperties(applyInfoMigration, appInfo);    // 经过测试 发现是可以拷贝非空属性的
        BeanUtils.copyProperties(applyInfoMigration, applyInfoDetail);  // 经过测试 相同的属性再次 拷贝过来,是会覆盖的
        System.out.println(appInfo.getV3_finger_time());
        Set<Map<String, String>> tempSet = appInfo.getJobinfo();
        tempSet.forEach(e -> {
            Map<String,String> tempMap = e;
            for (String key  :     tempMap.keySet()){
                System.out.println(key +  "其值为" + tempMap.get(key));
            }
        });
        System.out.println("---> detail信息");
       System.out.println(applyInfoDetail.getHistorytravelinfo_issuedate());
       tempSet = applyInfoDetail.getEducationinfo();
       tempSet.forEach(e -> {
           Map<String,String> tempMap = e;
           for (String key  :     tempMap.keySet()){
               System.out.println(key +  "其值为" + tempMap.get(key));
           }
       });
}

 

代码分析 : 首先准备1个能查询的数据实体,我们这里是ApplyInfoMigration,这是B表跟C表的合体pojo类,B表是基表只保存基本信息,C表示detail详细信息表,B表跟C表就一个UUID关联。这里我们测试了date类型跟set类型都可以拷贝成功,而且非空属性是不能拷贝的。

        经过上面测试,大部分情况下是可以适用的。下面我们再分析下源代码 : 

BeanUtils.copyProperties 源码分析 : 

private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
            throws BeansException {

        Assert.notNull(source, "Source must not be null");   // 断言判断  源类跟目标类是否为空 ,为空抛出异常,使用单元测试应该很熟悉这个东西
        Assert.notNull(target, "Target must not be null");

        Class<?> actualEditable = target.getClass();         // 获取目标类class
        
        // instanceof 运算符是用来在运行时指出对象是否是特定类的一个实例 (这个对象是否是这个特定类或者是它的子类的一个实例。)
        // 如果 object 是 class 的一个实例,则 instanceof 运算符返回 true。如果 object 不是指定类的一个实例,或者 object 是 null,则返回 false。
        if (editable != null) {
            if (!editable.isInstance(target)) {
                throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
                        "] not assignable to Editable class [" + editable.getName() + "]");
            }
            actualEditable = editable;
        }
        // 看起来感觉没做什么,但是感觉如果 editable 是target的父类的话,这就把target 目标类转换成了父类?
        
        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);    // 获取到目标类的属性  PS:以后知道这个API了
        List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);   // 需要忽略的属性(就是不想copy 赋值的属性)

        for (PropertyDescriptor targetPd : targetPds) {
            Method writeMethod = targetPd.getWriteMethod();   // 从属性上获取到set方法
            if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {    // 从忽略的属性集合中过滤
                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());  // 获取到目标类的具体属性(传参了)
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();   // 从源类上获取到 get方法
                    if (readMethod != null &&
                            ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                                // isAssignable(Class<?> cls, Class<?> toClass) 调用ClassUtils.class的isAssignable看是否能转型
                                //  System.out.println(ClassUtils.isAssignable(Date.class, Object.class)); //= true  
                                //  System.out.println(ClassUtils.isAssignable(Object.class, Date.class)); //=false
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {  // 获取到 源类的定义类的方法的语言修饰符
                                readMethod.setAccessible(true);  // 置为true:取消java语言访问检查,否则这里会抛异常的
                                /*
                                例如要序列化的时候,我们又必须有能力去处理这些字段,
                                这时候,我们就需要调用AccessibleObject上的setAccessible()方法来允许这种访问,
                                而由于反射类中的Field,Method和Constructor继承自AccessibleObject,
                                因此,通过在这些类上调用setAccessible()方法,我们可以实现对这些字段的操作。
                                但有的时候这将会成为一个安全隐患,为此,我们可以启用java.security.manager来判断程序是否具有调用setAccessible()的权限。
                                */
// 这里的
readMethod.setAccessible(true);必须要设置为true才行,我也写过反射调用方法进行set跟get但是必须先取消java语言访问检查才行,否则不能反射set值

} Object value = readMethod.invoke(source); // 从源类获取到值 if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); } writeMethod.invoke(target, value); // 把值插入到 目标类当中 } catch (Throwable ex) { throw new FatalBeanException( "Could not copy property '" + targetPd.getName() + "' from source to target", ex); } } } } } }

      既然,分析完源码,那我们能做些什么呢 ?  分析完源码,我们这里有个业务场景: 我们不想拷贝Null值,就是说B拷贝给A的一个J属性不为空,但是C继续拷贝给A,其中C有J属性并且值为NULL,其实我们在上面的代码中

writeMethod.invoke(target, value);   // 把值插入到 目标类当中 ,这这里对value进行NULL判断即可。
if(NULL!= value ){
writeMethod.invoke(target, value);  
}
这里基本就介绍完了,但是这个修改的源码怎么发布呢? 这就是另外的知识点,以后再做介绍,我们也可以把源码拷贝出去,使用自己的类来完成这个公共方法也行。
posted @ 2018-04-16 16:14  白云是世界的公民  阅读(271)  评论(0)    收藏  举报