likes
comments
collection
share

BeanUtils.copyProperties中的那些坑

作者站长头像
站长
· 阅读数 57

1.null值会被拷贝

Model1

import java.time.LocalDateTime;
import java.util.List;

/**
 * com.example
 * Description:
 *
 * @author jack
 * @date 2021/6/21 7:21 下午
 */
@Data
public class Model1 {

    private String name;

    private String password;

    private int age;

    private boolean vip;

    private LocalDateTime dateTime;

    private List<String> strList;

}

Model2

import java.time.LocalDateTime;
import java.util.List;

/**
 * com.example
 * Description:
 *
 * @author jack
 * @date 2021/6/21 7:22 下午
 */
@Data
public class Model2 {

    private String name;

    private String password;

    private Integer age;

    private Boolean vip;

    private LocalDateTime dateTime;

    private List<Object> strList;
}

null值拷贝测试 BeanUtilTest

import org.springframework.beans.BeanUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

/**
 * com.example
 * Description:
 *
 * @author jack
 * @date 2021/6/21 7:24 下午
 */
public class BeanUtilTest {
    public static void main(String[] args) {
        Model1 model1 = new Model1();
        model1.setName("张三");
        model1.setAge(22);
        List<String> strList = new ArrayList<>();
        strList.add("我是张三");
        model1.setStrList(strList);

        Model2 model2 = new Model2();
        model2.setDateTime(LocalDateTime.now());
        BeanUtils.copyProperties(model1, model2);
        System.out.println(model2);
    }
}

BeanUtils.copyProperties中的那些坑

model2中的dateTime属性变为了null

2.Boolean类型数据拷贝时可能会丢失

Model1Model2中的vip字段改为isVip,数据类型保持不变,一个为基本类型boolean一个为包装类型Boolean

private boolean isVip;

测试用例不变

BeanUtils.copyProperties中的那些坑

发现model1中的isVip并没有拷贝到model2

这里可能通过源码发现

BeanUtils.copyProperties中的那些坑

// 通过targetPd.getName() 没有获取到 sourcePd,这里的targetPd.getName()为isVip
getPropertyDescriptor(source.getClass(), targetPd.getName())

接着往下面走

BeanUtils.copyProperties中的那些坑

org.springframework.beans.CachedIntrospectionResults#getPropertyDescriptor 中,通过target对象中的isVip属性去source中查找时,并没有找到,导致没有拷贝,source中的 isVip字段被解析成了Vip

一直往下面走,走到java.beans.Introspector#getTargetPropertyInfo中的513行

BeanUtils.copyProperties中的那些坑

当属性类型为boolean时,属性名以is开头,属性名会去掉前面的is

所以 boolean类型 isXXX()会被解析为XXX,拷贝的时候需要留意。

解决方法:

  1. 修改Model2中的private Boolean isVip属性为Vip,因为Model1中的private boolean isVip被解析为Vip
  2. sourcetarget的属性名称和属性类型保持一致

3. 泛型类型拷贝的问题

target中的属性名称和source中的属性名称一致时,就会被拷贝,这个情况在开发中可能会出现问题,当使用target中的属性时,可能会报错java.lang.ClassCastException

解决方法:拷贝的的时候,忽略掉属性名称相同但类型不同的属性

BeanUtilsorg.springframework.beans.BeanUtils.copyProperties进行了一下封装,忽略null值,忽略属性名称一样但类型不一样

package com.example;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.StringUtils;

import java.beans.PropertyDescriptor;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * com.youzan.fx.trade.test
 * Description:
 *
 * @author jack
 * @date 2021/6/21 5:07 下午
 */
public class BeanUtils {

    private static final String BYTE = "byte";
    private static final String SHORT = "short";
    private static final String CHAR = "char";
    private static final String BOOLEAN = "boolean";
    private static final String INT = "int";
    private static final String LONG = "long";
    private static final String FLOAT = "float";
    private static final String DOUBLE = "double";


    /**
     * 忽略null值,忽略属性名称一样但类型不一样
     *
     * @param source source
     * @param target target
     */
    public static void copyPropertiesIgnoreNull(Object source, Object target) {
        org.springframework.beans.BeanUtils.copyProperties(source, target, getIgnoreFieldNames(source, target));
    }


    private static String[] getIgnoreFieldNames(Object source, Object target) {
        final BeanWrapper sourceWrapper = new BeanWrapperImpl(source);
        final BeanWrapper targetWrapper = new BeanWrapperImpl(target);
        PropertyDescriptor[] sourcePds = sourceWrapper.getPropertyDescriptors();
        PropertyDescriptor[] targetPds = targetWrapper.getPropertyDescriptors();

        Map<String, String> targetMap = new HashMap<>(targetPds.length);
        for (PropertyDescriptor targetPd : targetPds) {
            targetMap.put(targetPd.getName(), getFieldTypeName(targetPd.getReadMethod().getGenericReturnType().getTypeName()));
        }

        Set<String> ignoreFieldSet = new HashSet<>();
        for (PropertyDescriptor sourcePd : sourcePds) {
            Object srcValue = sourceWrapper.getPropertyValue(sourcePd.getName());
            if (srcValue == null) {
                ignoreFieldSet.add(sourcePd.getName());
            } else {
                String fieldTypeName = targetMap.get(sourcePd.getName());
                if (StringUtils.hasText(fieldTypeName) && !Objects.equals(fieldTypeName, getFieldTypeName(sourcePd.getReadMethod().getGenericReturnType().getTypeName()))) {
                    ignoreFieldSet.add(sourcePd.getName());
                }
            }
        }
        return ignoreFieldSet.toArray(new String[0]);
    }

    private static String getFieldTypeName(String fieldTypeName) {
        String typeName = "";
        switch (fieldTypeName) {
            case BYTE:
                typeName = Byte.class.getTypeName();
                break;
            case SHORT:
                typeName = Short.class.getTypeName();
                break;
            case CHAR:
                typeName = Character.class.getTypeName();
                break;
            case BOOLEAN:
                typeName = Boolean.class.getTypeName();
                break;
            case INT:
                typeName = Integer.class.getTypeName();
                break;
            case LONG:
                typeName = Long.class.getTypeName();
                break;
            case FLOAT:
                typeName = Float.class.getTypeName();
                break;
            case DOUBLE:
                typeName = Double.class.getTypeName();
                break;
            default:
                break;
        }
        if (StringUtils.hasText(typeName)) {
            return typeName;
        }
        return fieldTypeName;
    }
}

转载自:https://juejin.cn/post/6976243622890962952
评论
请登录