代码改变世界

BeanUtils

2019-10-08 20:51  Loull  阅读(389)  评论(0编辑  收藏  举报
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static org.springframework.beans.BeanUtils.getPropertyDescriptor;
import static org.springframework.beans.BeanUtils.getPropertyDescriptors;

public class BeanUtils {

    /**
     * List集合Bean转换
     *
     * @param sourceList 被转换Bean的集合
     * @param clazz      目标对象Class
     * @param <T>        目标对象类型
     * @return 目标对象集合
     */
    public static <T> List<T> convertBeanList(List<?> sourceList, Class<T> clazz) {
        return sourceList.stream()
            .map(r -> convertBean(r, clazz))
            .collect(Collectors.toList());
    }

    /**
     * 单Bean转换
     *
     * @param source 被转换对象
     * @param clazz  目标对象Class
     * @param <T>    目标对象类型
     * @return 目标对象
     */
    public static <T> T convertBean(Object source, Class<T> clazz) {
        if (null == source) {
            return null;
        }
        T bean = (T)org.springframework.beans.BeanUtils.instantiateClass(clazz);
        copyProperties(source, bean, true);
        return bean;
    }

    /**
     * 合并对象中属性到目标对象中
     *
     * @param target  目标对象
     * @param sources 源对象列表
     * @throws BeansException
     */
    public static void merge(Object target, Object... sources) throws BeansException {
        for (Object source : sources) {
            copyProperties(source, target, false);
        }
    }

    /**
     * 合并对象中属性到目标对象中
     *
     * @param targetClass 目标对象
     * @param sources     源对象列表
     * @throws BeansException
     */
    public static <T> T merge(Class<T> targetClass, Object... sources) throws BeansException {
        T target = (T)org.springframework.beans.BeanUtils.instantiateClass(targetClass);
        for (Object source : sources) {
            copyProperties(source, target, false);
        }
        return target;
    }

    /**
     * Copy the property values of the given source bean into the target bean.
     * <p>Note: The source and target classes do not have to match or even be derived
     * from each other, as long as the properties match. Any bean properties that the
     * source bean exposes but the target bean does not will silently be ignored.
     * <p>This is just a convenience method. For more complex transfer needs,
     * consider using a full BeanWrapper.
     *
     * @param source the source bean
     * @param target the target bean
     * @throws BeansException if the copying failed
     * @see BeanWrapper
     */
    private static void copyProperties(Object source, Object target, boolean copyNull) throws BeansException {
        copyProperties(source, target, copyNull, null, (String[])null);
    }

    /**
     * Copy the property values of the given source bean into the given target bean.
     * <p>Note: The source and target classes do not have to match or even be derived
     * from each other, as long as the properties match. Any bean properties that the
     * source bean exposes but the target bean does not will silently be ignored.
     *
     * @param source           the source bean
     * @param target           the target bean
     * @param editable         the class (or interface) to restrict property setting to
     * @param ignoreProperties array of property names to ignore
     * @throws BeansException if the copying failed
     * @see BeanWrapper
     */
    @SuppressWarnings("unchecked")
    private static void copyProperties(Object source, Object target, boolean copyNull, 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();
        if (editable != null) {
            if (!editable.isInstance(target)) {
                throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
                    "] not assignable to Editable class [" + editable.getName() + "]");
            }
            actualEditable = editable;
        }
        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
        List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);

        for (PropertyDescriptor targetPd : targetPds) {
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null) {
                        //当类型可以直接转换时
                        if (ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                            try {
                                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                    readMethod.setAccessible(true);
                                }
                                Object value = readMethod.invoke(source);
                                if (value == null && !copyNull) {
                                    continue;
                                }

                                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);
                            }
                        } else {
                            try {
                                if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                    readMethod.setAccessible(true);
                                }
                                Object value = readMethod.invoke(source);

                                if (value == null && !copyNull) {
                                    continue;
                                }
                                //扩展的枚举相关逻辑
                                if (Objects.equals(writeMethod.getParameterTypes()[0], String.class)
                                    && value instanceof Enum) {
                                    if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                        writeMethod.setAccessible(true);
                                    }
                                    Enum<?> enumValue = (Enum<?>)value;
                                    writeMethod.invoke(target, enumValue.name());
                                } else if (value instanceof String
                                    && writeMethod.getParameterTypes()[0].isEnum()) {
                                    if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                        writeMethod.setAccessible(true);
                                    }
                                    String stringValue = (String)value;
                                    if (StringUtils.isBlank(stringValue)) {
                                        continue;
                                    }
                                    Class<Enum> enumTargetType = (Class<Enum>)writeMethod.getParameterTypes()[0];

                                    writeMethod.invoke(target, convertStringToEnum(enumTargetType, stringValue));
                                }

                            } catch (Throwable ex) {
                                throw new FatalBeanException(
                                    "Could not copy property '" + targetPd.getName() + "' from source to target", ex);
                            }
                        }
                    }
                }
            }
        }
    }

    private static Enum convertStringToEnum(Class<Enum> clazz, String s) {
        if (Enum.class.isAssignableFrom(clazz)) {
            return Enum.valueOf(clazz, s);
        }
        return null;
    }
}