1. Null values are copied

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;

}
Copy the code

Model2

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

/**
 * com.example
 * Description:
 *
 * @author jack
 * @date2021/6/21 7:22pm */
@Data
public class Model2 {

    private String name;

    private String password;

    private Integer age;

    private Boolean vip;

    private LocalDateTime dateTime;

    private List<Object> strList;
}
Copy the code

Null value copy test BeanUtilTest

import org.springframework.beans.BeanUtils;

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

/**
 * com.example
 * Description:
 *
 * @author jack
 * @date2021/6/21 7:24pm */
public class BeanUtilTest {
    public static void main(String[] args) {
        Model1 model1 = new Model1();
        model1.setName("Zhang");
        model1.setAge(22);
        List<String> strList = new ArrayList<>();
        strList.add("I'm John.");
        model1.setStrList(strList);

        Model2 model2 = newModel2(); model2.setDateTime(LocalDateTime.now()); BeanUtils.copyProperties(model1, model2); System.out.println(model2); }}Copy the code

The dateTime attribute in Model2 becomes NULL

2.Boolean data may be lost during copy

Change the VIP field in Model1 and Model2 to isVip, and keep the data type unchanged. One is the basic type Boolean and the other is the wrapper type Boolean

private boolean isVip;
Copy the code

The test case is unchanged

It is found that the isVip in Model1 has not been copied to Model2

This may be found in the source code

// sourcePd is not obtained by targetpd.getName (), where targetpD.getName () is isVip
getPropertyDescriptor(source.getClass(), targetPd.getName())
Copy the code

Keep going down

In the org. Springframework. Beans. CachedIntrospectionResults# getPropertyDescriptor, through isVip attributes of target object to find the source, the and couldn’t find it, The isVip field in the source is resolved to Vip

Go straight down to line 513 in the java.beans.Introspector#getTargetPropertyInfo

If the property type is Boolean, the property name starts with is, and the preceding is is removed

So the Boolean isXXX() will parse to XXX, so be careful when copying.

Solution:

  1. Modify theModel2In theprivate Boolean isVipProperties forVipBecause theModel1In theprivate boolean isVipIs resolved as aVipthe
  2. sourceandtargetThe attribute name must be consistent with the attribute type

3. Generic type copy problems

When the target of the attribute name and attribute name of the source, can be copied, this situation may arise in the development, when use of the target attribute, may be an error. The Java lang. ClassCastException

Solution: when copying, ignore the properties with the same name but different types

BeanUtils org. Springframework. Beans. BeanUtils. CopyProperties for encapsulation, ignore null values, as a different but ignore the attribute name

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";


    /** * Ignore null values, ignore properties with the same name but different types **@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;
        }
        returnfieldTypeName; }}Copy the code