java反射之反省解释器

关于beanUtils的copyproperties()方法,各大软件机构都有自己的源码,这里不在说他们的优缺点了。本次主要分享一个经过参考spring框架的beanUtils.copyProperties()方法,进行简化抽离的类复制方法。

spring框架的beanUtils.copyProperties()方法,之所以快,是因为加入了缓存机制以及使用了java提供的(set和get方法专用反射反省类-propertyDescriptor)。关于这个类机制,我也不知道,不顾既然java提供了那就用就完事了。

直接附上代码—代码过于简单,就不再讲述原理。

package com.xilidata.products.cocc.mtds.util;

import com.xilidata.core.foundation.exceptions.DataValidateException;
import com.xilidata.core.foundation.utils.ValidationUtil;
import com.xilidata.products.cocc.mtds.model.mtds.ItemBillModel;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

public class BeanUtil {

    private static final Logger logger = LoggerFactory.getLogger(BeanUtil.class);

    private static boolean defaultReplace = false;

    private static boolean defaultCache = true;

    //    private static final ConcurrentHashMap<String, ConcurrentHashMap<String, CacheMethod>> cacheMap = new ConcurrentHashMap<>();
    private static final ConcurrentHashMap<String, ConcurrentHashMap<String, CacheMethodV2>> cacheMapV2 = new ConcurrentHashMap<>();

    public static <T> T copyProperties(Object source, Class<T> tClass) throws DataValidateException {
        return copyProperties(source, tClass, null, null, defaultReplace, defaultCache);
    }

    public static <T> T copyProperties(Object source, Class<T> tClass, List<Class<?>> ignoreClazzs) throws DataValidateException {
        return copyProperties(source, tClass, ignoreClazzs, null, defaultReplace, defaultCache);
    }

    public static <T> T copyProperties(Object source, Class<T> tClass, List<Class<?>> ignoreClazzs,
                                       List<String> ignoreFields) throws DataValidateException {
        return copyProperties(source, tClass, ignoreClazzs, ignoreFields, defaultReplace, defaultCache);
    }

    public static <T> T copyProperties(Object source, Class<T> tClass, List<Class<?>> ignoreClazzs,
                                       List<String> ignoreFields, boolean replaceAll) throws DataValidateException {
        return copyProperties(source, tClass, ignoreClazzs, ignoreFields, replaceAll, defaultCache);
    }

    public static <T> T copyProperties(Object source, Class<T> tClass, List<Class<?>> ignoreClazzs,
                                       List<String> ignoreFields, boolean replaceAll, boolean openCache) throws DataValidateException {
        try {
            Constructor<T> constructor = tClass.getConstructor();
            return copyProperties(source, constructor.newInstance(), ignoreClazzs, ignoreFields, replaceAll, openCache);
        } catch (NoSuchMethodException e) {
            // 找不到构造方法
            throw new DataValidateException("目标类校验:没有对应的无参构造");
        } catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
            throw new DataValidateException("目标类校验:构造执行异常");
        }
    }

    public static <T> T copyProperties(Object source, T target) throws DataValidateException {
        return copyProperties(source, target, null, null, defaultReplace, defaultCache);
    }

    public static <T> T copyProperties(Object source, T target, List<Class<?>> ignoreClazzs) throws DataValidateException {
        return copyProperties(source, target, ignoreClazzs, null, defaultReplace, defaultCache);
    }

    public static <T> T copyProperties(Object source, T target, List<Class<?>> ignoreClazzs, List<String> ignoreFields) throws DataValidateException {
        return copyProperties(source, target, ignoreClazzs, ignoreFields, defaultReplace, defaultCache);
    }


    /**
     * @param source       被复制的对象
     * @param target       需要复制的对象
     * @param ignoreClazzs 过滤不参与复制字段的class
     * @param ignoreFields 过滤不参与复制的字段
     * @param replaceAll   是否全部覆盖,默认false 为null不复制
     * @param openCache    是否开始缓存 默认开启
     * @throws DataValidateException
     */
    public static <T> T copyProperties(Object source, T target, List<Class<?>> ignoreClazzs, List<String> ignoreFields, boolean replaceAll, boolean openCache) throws DataValidateException {
        if (openCache) {
            return copyPropertiesV3(source, target, ignoreClazzs, ignoreFields, replaceAll, true);
        } else {
            return copyPropertiesV1(source, target, ignoreClazzs, ignoreFields, replaceAll);
        }
    }

    private static <T> T copyPropertiesV1(Object source, T target, List<Class<?>> ignoreClazzs, List<String> ignoreFields,
                                          boolean replaceAll) throws DataValidateException {
        if (source == null || target == null) {
            throw new DataValidateException("参数对象不可为空");
        }
        Class<?> sourceClass = source.getClass();
        Class<?> targetClass = target.getClass();
        Class<?> tempClass = sourceClass;
        // 找到所有的父级包含的字段
        Map<String, Class<?>> filedMap = new HashMap<>();
        out:
        do {
            if (!ValidationUtil.isEmpty(ignoreClazzs)) {
                for (Class<?> ignoreClazz : ignoreClazzs) {
                    if (tempClass.equals(ignoreClazz)) {
                        continue out;
                    }
                }
            }
            inner:
            for (Field filed : tempClass.getDeclaredFields()) {
                if (!ValidationUtil.isEmpty(ignoreFields)) {
                    for (String ignoreField : ignoreFields) {
                        if (ignoreField.equals(filed.getName())) {
                            continue inner;
                        }
                    }
                }
                filedMap.put(filed.getName(), filed.getType());
            }
        } while (!Object.class.equals(tempClass = tempClass.getSuperclass()));
        Set<Map.Entry<String, Class<?>>> entries = filedMap.entrySet();
        for (Map.Entry<String, Class<?>> entry : entries) {
            try {
                Method getMethod = sourceClass.getMethod(getterStr(entry.getKey()));
                Object value = getMethod.invoke(source);
                if (replaceAll || null != value) {
                    Method setMethod = targetClass.getMethod(setterStr(entry.getKey()), entry.getValue());
                    setMethod.invoke(target, value);
                }
            } catch (InvocationTargetException e) {
//                方法调用失败
//                e.printStackTrace();
//                logger.warn("字段:{} 异常 方法调用失败", entry.getKey());
            } catch (IllegalAccessException e) {
//                方法不可访问
//                e.printStackTrace();
//                logger.warn("字段:{} 异常 方法不可访问", entry.getKey());
            } catch (NoSuchMethodException e) {
//              找不到set、get方法
//                e.printStackTrace();
//                logger.warn("字段:{} 异常 找不到set、get方法", entry.getKey());
            }
        }
        return target;
    }

    private static <T> T copyPropertiesV3(Object source, T target, List<Class<?>> ignoreClazzs, List<String> ignoreFields,
                                          boolean replaceAll, boolean openCache) throws DataValidateException {
        return copyPropertiesV3(source, target, ignoreClazzs, ignoreFields, replaceAll, openCache, null, null, false);
    }


    public static <T1, T> List<T> copyPropertiesBatch(List<T1> sources, Class<T> targetClass) throws DataValidateException {
        return copyPropertiesBatch(sources, targetClass, null, null, defaultReplace, defaultCache);
    }

    public static <T1, T> List<T> copyPropertiesBatch(List<T1> sources, Class<T> targetClass,
                                                      List<Class<?>> ignoreClazzs) throws DataValidateException {
        return copyPropertiesBatch(sources, targetClass, ignoreClazzs, null, defaultReplace, defaultCache);
    }

    public static <T1, T> List<T> copyPropertiesBatch(List<T1> sources, Class<T> targetClass, List<Class<?>> ignoreClazzs,
                                                      List<String> ignoreFields) throws DataValidateException {
        return copyPropertiesBatch(sources, targetClass, ignoreClazzs, ignoreFields, defaultReplace, defaultCache);
    }

    public static <T1, T> List<T> copyPropertiesBatch(List<T1> sources, Class<T> targetClass, List<Class<?>>
            ignoreClazzs, List<String> ignoreFields,
                                                      boolean replaceAll) throws DataValidateException {
        return copyPropertiesBatch(sources, targetClass, ignoreClazzs, ignoreFields, replaceAll, defaultCache);

    }


    public static <T1, T> List<T> copyPropertiesBatch(List<T1> sources, Class<T> targetClass, List<Class<?>>
            ignoreClazzs, List<String> ignoreFields, boolean replaceAll, boolean openCache) throws DataValidateException {
        if (null == sources || sources.size() == 0) {
            return Collections.emptyList();
        }
        T1 t1 = sources.get(0);
        Class<?> aClass = t1.getClass();
        ConcurrentHashMap<String, CacheMethodV2> getConcurrentHashMap = cacheMapV2.get(aClass.getName());
        if (ValidationUtil.isEmpty(getConcurrentHashMap)) {
            getConcurrentHashMap = cacheMapV2.put(aClass.getName(), getAll(aClass));
        }
        ConcurrentHashMap<String, CacheMethodV2> setConcurrentHashMap;
        if (aClass.getName().equals(targetClass.getName())) {
            setConcurrentHashMap = getConcurrentHashMap;
        } else {
            setConcurrentHashMap = cacheMapV2.get(targetClass.getName());
            if (ValidationUtil.isEmpty(setConcurrentHashMap)) {
                setConcurrentHashMap = cacheMapV2.put(targetClass.getName(), getAll(targetClass));
            }
        }
        List<T> list = new ArrayList<>();
        T target;
        Constructor<T> constructor;
        try {
            constructor = targetClass.getConstructor();
        } catch (NoSuchMethodException e) {
            throw new DataValidateException("target class can not be instance");
        }
        try {
            for (T1 t11 : sources) {
                target = constructor.newInstance();
                copyPropertiesV3(t11, target, ignoreClazzs, ignoreFields, replaceAll, openCache, getConcurrentHashMap, setConcurrentHashMap, true);
                list.add(target);
            }
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new DataValidateException("target class can not be instance");
        }
        return list;
    }

    public static <T> Map<String, T> mapToMap(Map<String, T> sourceMap, Map<String, T> targetMap) {
        return mapToMap(sourceMap, targetMap, null);
    }

    public static <T> Map<String, T> mapToMap(Map<String, T> sourceMap, Map<String, T> targetMap, List<String> fields) {
        if (null == sourceMap) {
            return targetMap;
        }
        if (null == targetMap) {
            return null;
        }
        outer:
        for (Map.Entry<String, T> entry : sourceMap.entrySet()) {
            if (null != fields) {
                for (String field : fields) {
                    if (Objects.equals(entry.getKey(), field)) {
                        continue outer;
                    }
                }
            }
            targetMap.put(entry.getKey(), entry.getValue());
        }
        return targetMap;
    }

    public static <T> T mapToJavaObject(Map<String, Object> map, Class<T> clazz) {
        return mapToJavaObject(map, clazz, null, null);
    }

    public static <T> T mapToJavaObject(Map<String, Object> map, Class<T> clazz, List<String>
            ignoreFields, List<Class<?>> ignoreClazzs) {
        try {
            T t = clazz.getConstructor().newInstance();
            return mapToJavaObject(map, t, ignoreFields, ignoreClazzs);
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            return null;
        }
    }

    public static <T> T mapToJavaObject(Map<String, Object> map, T t, List<String>
            ignoreFields, List<Class<?>> ignoreClazzs) {
        if (t == null) {
            return null;
        }
        Class<?> clazz = t.getClass();
        ConcurrentHashMap<String, CacheMethodV2> setMap = cacheMapV2.get(clazz.getName());
        if (setMap == null) {
            setMap = getAll(clazz);
            cacheMapV2.put(clazz.getName(), setMap);
        }
        CacheMethodV2 methodV2;
        Object value;
        outer:
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (null != ignoreFields) {
                for (String field : ignoreFields) {
                    if (Objects.equals(field, entry.getKey())) {
                        continue outer;
                    }
                }
            }
            if (entry.getKey() == null) {
                continue;
            }
            value = entry.getValue();
            methodV2 = setMap.get(entry.getKey());
            if (null == methodV2) {
                continue;
            }
            if (null != ignoreClazzs) {
                for (Class<?> aClass : ignoreClazzs) {
                    if (Objects.equals(aClass.getName(), methodV2.getClassName())) {
                        continue outer;
                    }
                }
            }
            try {
                if (value != null) {
                    methodV2.getWriteMethod().invoke(t, value);
                }
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        return t;
    }

    private static <T> T copyPropertiesV3(Object source, T target, List<Class<?>>
            ignoreClazzs, List<String> ignoreFields, boolean replaceAll, boolean openCache,
                                          ConcurrentHashMap<String, CacheMethodV2> getConcurrentHashMap,
                                          ConcurrentHashMap<String, CacheMethodV2> setConcurrentHashMap, boolean enableContinue) throws DataValidateException {
        if (source == null || target == null) {
            throw new DataValidateException("参数对象不可为空");
        }
        boolean isMap = target instanceof Map;
        Class<?> sourceClass = source.getClass();
        Class<?> targetClass = target.getClass();
        if (ValidationUtil.isEmpty(getConcurrentHashMap)) {
            if (ValidationUtil.isEmpty(getConcurrentHashMap = cacheMapV2.get(sourceClass.getName()))) {
                getConcurrentHashMap = getAll(sourceClass);
                if (openCache) {
                    cacheMapV2.put(sourceClass.getName(), getConcurrentHashMap);
                }
            }

        }
        if (sourceClass.equals(targetClass)) {
            setConcurrentHashMap = getConcurrentHashMap;
        } else if (ValidationUtil.isEmpty(setConcurrentHashMap)) {
            if (ValidationUtil.isEmpty(setConcurrentHashMap = cacheMapV2.get(targetClass.getName()))) {
                setConcurrentHashMap = getAll(targetClass);
                if (openCache) {
                    cacheMapV2.put(targetClass.getName(), setConcurrentHashMap);
                }
            }

        }
        out:
        for (Map.Entry<String, CacheMethodV2> entry : getConcurrentHashMap.entrySet()) {
            if (!ValidationUtil.isEmpty(ignoreClazzs)) {
                for (Class<?> ignoreClazz : ignoreClazzs) {
                    if (entry.getValue().getClassName().equals(ignoreClazz.getName())) {
                        continue out;
                    }
                }
            }
            if (!ValidationUtil.isEmpty(ignoreFields)) {
                for (String ignoreField : ignoreFields) {
                    if (ignoreField.equals(entry.getKey())) {
                        continue out;
                    }
                }
            }
            try {
                CacheMethodV2 cacheMethodV2 = setConcurrentHashMap.get(entry.getKey());
                if (null == cacheMethodV2) {
                    continue;
                }
                Method setMethod = null;
                Object value = entry.getValue().getReadMethod().invoke(source);
                if (value instanceof List) {
                    List<?> list = (List<? extends Object>) value;
                    if (list.size() > 0) {
                        value = copyPropertiesBatch(list, list.get(0).getClass());
                    }
                } else if (value instanceof BigDecimal) {
                    value = new BigDecimal(((BigDecimal) value).toPlainString());
                } else if (value instanceof BigInteger) {
                    value = BigInteger.valueOf(((BigInteger) value).longValue());
                } else if (value instanceof String) {
                    value = String.valueOf(value);
                } else if (value != null && !isBaseType(value)) {
                    try {
                        Constructor<?> constructor = cacheMethodV2.getReadMethod().getReturnType().getConstructor();
                        Object o = constructor.newInstance();
                        copyProperties(value, o);
                        value = o;
                    } catch (NoSuchMethodException | InstantiationException e) {
                        continue;
                    }
                } else if (value instanceof Map) {
                    if (isMap) {
                        value = mapToMap((Map<String, Object>) value, new HashMap<String, Object>());
                    } else {
                        value = mapToJavaObject((Map<String, Object>) value, cacheMethodV2.getReadMethod().getReturnType());
                    }
                }
                if (!enableContinue && replaceAll && null == value) {
                    setMethod = cacheMethodV2.getWMethod();
                    if (null == setMethod && cacheMethodV2.getEnableSet()) {
                        try {
                            setMethod = targetClass.getMethod(setterStr(entry.getKey()), cacheMethodV2.getWriteMethod().getParameterTypes()[0]);
                        } catch (NoSuchMethodException e) {
                            cacheMethodV2.setEnableSet(false);
                            continue;
                        }
                        cacheMethodV2.setWMethod(setMethod);
                    }
                } else if (null != value) {
                    setMethod = cacheMethodV2.getWriteMethod();
                }
                if (null != setMethod) {
                    setMethod.invoke(target, value);
                }
            } catch (IllegalAccessException e) {
//                e.printStackTrace();
//                logger.warn("字段:{} 异常 方法不可访问", entry.getKey());
            } catch (InvocationTargetException e) {
//                e.printStackTrace();
//                logger.warn("字段:{} 异常 set/get方法调用异常", entry.getKey());
            }

        }
        return target;
    }

    private static boolean isBaseType(Object obj) {
        if (null == obj) {
            return false;
        }
        return isBaseType(obj.getClass());
    }

    private static boolean isBaseType(Class<?> clazz) {
//        if (BigDecimal.class.equals(clazz)) {
//            return true;
//        }
        if (clazz != null) {
            try {
                Field type = clazz.getField("TYPE");
                return ((Class<?>) type.get(null)).isPrimitive();
            } catch (NoSuchFieldException | IllegalAccessException e) {
                return false;
            }
        }
        return false;
    }

    private static ConcurrentHashMap<String, CacheMethodV2> getAll(Class<?> clazz) {
        ConcurrentHashMap<String, CacheMethodV2> map = new ConcurrentHashMap<>(16 * 4 / 3 + 1);
        Class<?> tempClazz = clazz;
        do {
            for (Field field : tempClazz.getDeclaredFields()) {
                try {
                    map.put(field.getName(), new CacheMethodV2(field.getName(), tempClazz.getName(), tempClazz));
                } catch (IntrospectionException e) {
                    // 反省异常
//                        e.printStackTrace();
//                        logger.warn("字段:{} 类:{} 反省异常", field.getName(), sourceClass);
                }
            }

        } while (!Object.class.equals(tempClazz = tempClazz.getSuperclass()));
        return map;
    }

    public static <K, V> V getOrSetDefault(AbstractMap<K, V> map, K k, V defaultV) {
        return map.computeIfAbsent(k, k1 -> defaultV);
    }

    public static Method getWriteMethod(String propertyName, Class<?> beanClass) {
        try {
            PropertyDescriptor pd = new PropertyDescriptor(propertyName, beanClass);
            return pd.getWriteMethod();
        } catch (IntrospectionException e) {
        }
        return null;
    }

    public static Method getReadMethod(String propertyName, Class<?> beanClass) {
        try {
            PropertyDescriptor pd = new PropertyDescriptor(propertyName, beanClass);
            return pd.getReadMethod();
        } catch (IntrospectionException e) {
        }
        return null;
    }

    public static PropertyDescriptor getPropertyDescriptor(String propertyName, Class<?> beanClass) {
        try {
            PropertyDescriptor pd = new PropertyDescriptor(propertyName, beanClass);
            return pd;
        } catch (IntrospectionException e) {
        }
        return null;
    }

    public static String getterStr(String str) {
        char[] chars = str.toCharArray();
        if (chars[0] >= 97 && chars[0] <= 122) {
            chars[0] -= 32;
        }
        return "get" + String.valueOf(chars);
    }

    public static String setterStr(String str) {
        char[] chars = str.toCharArray();
        if (chars[0] >= 97 && chars[0] <= 122) {
            chars[0] -= 32;
        }
        return "set" + String.valueOf(chars);
    }


    public static void clearCache() {
        cacheMapV2.clear();
    }

    @Getter
    private static class CacheMethodV2 {
        /**
         * 字段名
         */
        private final String name;
        /**
         * 属性解释器
         */
        private final PropertyDescriptor pd;
        /**
         * 字段实际在那个类
         */
        private final String className;

        private Method WMethod;

        private Method writeMethod;

        private Method readMethod;

        private boolean enableSet = true;

        public CacheMethodV2(String name, PropertyDescriptor pd, String className) {
            this.name = name;
            this.pd = pd;
            this.className = className;
        }

        public CacheMethodV2(String name, String className, Class<?> clazz) throws IntrospectionException {
            this.name = name;
            this.className = className;
            this.pd = new PropertyDescriptor(name, clazz);
        }

        public final Method getReadMethod() {
            if (null != readMethod) {
                return readMethod;
            }
            return readMethod = pd.getReadMethod();
        }

        public final Method getWriteMethod() {
            if (null != writeMethod) {
                return writeMethod;
            }
            return writeMethod = pd.getWriteMethod();
        }

        public void setWMethod(Method m) {
            this.WMethod = m;
        }

        public Method getWMethod() {
            return this.WMethod;
        }

        public Method getWMethodOrDefault(Function<CacheMethodV2, Method> fun) {
            return fun.apply(this);
        }

        public boolean getEnableSet() {
            return enableSet;
        }

        public void setEnableSet(boolean enableSet) {
            this.enableSet = enableSet;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof CacheMethodV2)) return false;
            CacheMethodV2 that = (CacheMethodV2) o;
            return Objects.equals(name, that.name) && Objects.equals(className, that.className);
        }

        @Override
        public int hashCode() {
            return Objects.hash(name, className);
        }
    }


}

1 个赞