属性复制工具类

有时可能需要复制一个对象的属性到另一个对象,使用 Spring 的 BeanUtils 工具类可以很容易实现,但是这样复制的属性如果是对象的话,可能会导致不安全的共享,为此,可以通过为每个属性对象实例化对应的对象来达到这一目的

具体代码如下所示:

import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.time.chrono.ChronoLocalDate;
import java.time.chrono.ChronoLocalDateTime;
import java.util.Set;

public class PropertiesTool {
    private final static Logger log = LoggerFactory.getLogger(PropertiesTool.class);

    public static void copyProperties(Object src, Object target) {
        copyProperties(src, target, true);
    }

    /**
     * 将一个原对象的所有非静态属性(包括父类属性)复制到目标对象,复制属性时,通过属性的类型和名称来
     * 判断是否是相同的属性<br />
     * 这里的属性复制为深度拷贝,但这并不意味着一定会创建新的实例属性。因为对于不可变的对象,在多个对象
     * 之间进行共享是安全的,并且这些不可变对象一般都不会提供直接的实例化构造函数,因此对于不可变类的属性
     * 将会直接复制对象引用而不是重新实例化新的实例
     *
     * @param src      具备相关属性值的实例对象
     * @param target   将要填充属性值的实例对象
     * @param copyNull 控制开关,是否需要将 null 值拷贝到目标对象
     * @apiNote 复制的属性将会覆盖所有的非静态字段,即使这些字段属性被 final 修饰
     */
    public static void copyProperties(Object src, Object target, boolean copyNull) {
        copyProperties(src, src.getClass(), target, target.getClass(), copyNull);
    }

    private static void copyProperties(
            Object src, Class<?> srcClass,
            Object target, Class<?> tarClass,
            boolean copyNull
    ) {
        if (src == null || target == null) return;
        if (srcClass == null || tarClass == null) return;

        Field[] sfs = srcClass.getDeclaredFields();
        Set<Field> fieldSet = Sets.newHashSet(tarClass.getDeclaredFields());
        for (Field tf : sfs) {
            // 目标对象不包含的属性字段需要跳过
            if (!fieldSet.contains(tf)) continue;

            int modifiers = tf.getModifiers();

            // 过滤静态字段
            if ((modifiers & Modifier.STATIC) != 0) continue;

            try {
                tf.setAccessible(true);
                Object val = tf.get(src);

                // 是否将空值复制到目标对象的对象的处理
                if (copyNull && val == null) continue;

                Class<?> fc = tf.getType();
                if (!isConstType(fc)) {
                    if (fc.isArray()) {
                        tf.set(target, cloneArray(val));
                        continue;
                    }

                    // 对于对象类型的深度属性复制,需要相关的对象具有对应的无参构造函数
                    Object obj = fc.getDeclaredConstructor().newInstance();
                    copyProperties(val, obj);
                    tf.set(target, obj);
                    continue;
                }

                tf.set(target, val); // 基本类型的特殊处理
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        copyProperties(src, srcClass.getSuperclass(), target, tarClass.getSuperclass(), copyNull);
    }

    private static boolean isConstType(Class<?> clazz) {
        if (clazz.isPrimitive()) return true;

        // LocalDate 系列不可变对象的处理
        if (ChronoLocalDateTime.class.isAssignableFrom(clazz)) return true;
        if (ChronoLocalDate.class.isAssignableFrom(clazz)) return true;

        // 数值型的类型是不可变的,可以在多个对象之间安全地进行共享
        if (Number.class.isAssignableFrom(clazz)) return true;

        // String 在这里被视为基本数据类型,因为它是不可变的,可以安全地在多个对象之间共享
        return clazz == String.class;
    }

    private enum Clazz {
        BYTE(byte.class),
        BOOLEAN(boolean.class),
        SHORT(short.class),
        CHAR(char.class),
        FLOAT(float.class),
        INT(int.class),
        LONG(long.class),
        DOUBLE(double.class);

        public final Class<?> clazz;

        Clazz(Class<?> clazz) {
            this.clazz = clazz;
        }
    }

    /**
     * 实现数组对象的深度拷贝,数组中元素类型,应当至少存在一个无参构造函数,
     * 即使这个构造函数是私有的同样可以满足要求
     *
     * @param obj 待拷贝的数组对象
     * @return 拷贝之后的数组对象
     */
    private static Object cloneArray(Object obj) {
        if (!obj.getClass().isArray()) {
            throw new IllegalArgumentException("不能将非数组类型的对象转换为对应的 List");
        }

        Class<?> compClazz = obj.getClass().getComponentType();
        if (compClazz.isPrimitive()) {
            Clazz type = Clazz.valueOf(Clazz.class, compClazz.getSimpleName().toUpperCase());

            Object res = null;
            switch (type) {
                case BYTE:
                    res = ((byte[]) obj).clone();
                    break;
                case BOOLEAN:
                    res = ((boolean[]) obj).clone();
                    break;
                case SHORT:
                    res = ((short[]) obj).clone();
                    break;
                case CHAR:
                    res = ((char[]) obj).clone();
                    break;
                case FLOAT:
                    res = ((float[]) obj).clone();
                    break;
                case INT:
                    res = ((int[]) obj).clone();
                    break;
                case LONG:
                    res = ((long[]) obj).clone();
                    break;
                case DOUBLE:
                    res = ((double[]) obj).clone();
                    break;
            }

            return res;
        }

        int len = Array.getLength(obj);
        if (len <= 0) return null;

        Class<?> clazz = Array.get(obj, 0).getClass();
        Object ans = Array.newInstance(clazz, len);
        for (int i = 0; i < len; i++) {
            Object val = Array.get(obj, i); // 当前属性中,数组中指定位置的对象

            // 基本类型直接复制即可
            if (isConstType(clazz)) {
                Array.set(ans, i, val);
                continue;
            }

            // 多维数组的处理
            if (clazz.isArray()) {
                Array.set(ans, i, cloneArray(val));
                continue;
            }

            try {
                /*
                 * 针对数组中元素类型为一般对象,那么首先通过反射的方式实例化该位置的数组元素,
                 * 然后将该位置的原对象属性复制到这个对象中,从而实现数组对象的深度拷贝
                 */
                Object o = clazz.getDeclaredConstructor().newInstance();
                copyProperties(val, o);
                Array.set(ans, i, o);
            } catch (Throwable t) {
                log.info("类型 {} 不存在相关的无参构造函数,无法完成数组对象的深度拷贝", clazz);
                t.printStackTrace();
            }
        }

        return ans;
    }
}
posted @ 2022-06-02 23:06  FatalFlower  阅读(120)  评论(0编辑  收藏  举报