一款简单高效的BeanCopy工具

540 阅读2分钟

曾有网友详细比对过各种属性copy工具(Apache BeanUtils、PropertyUtils, Spring BeanUtils, Cglib BeanCopier),Cglib的BeanCopier表现可谓相当优秀,原博文:Bean复制的几种框架性能比较

但其直接使用却稍显繁琐,所以这里做了一些简单封装,以供参考,废话少说,show code:

package com.cafeboy.common.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.cglib.core.Converter;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Bean转换器
 */
public class BeanConverter {

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

    private static final ConcurrentHashMap<String, BeanCopier> copierMap = new ConcurrentHashMap<>();

    /**
     * BeanCopier.create()的过程相对比较耗时,所以尽量避免多次初始化
     */
    private static <S, T> BeanCopier getTheBeanCopier(S source, T target, boolean ignoreNull) {
        String key = source.getClass().getName() + "_" + target.getClass().getName() + "_" + ignoreNull;
        BeanCopier copier = copierMap.get(key);
        if (null == copier) {
            synchronized (copierMap) {
                copier = BeanCopier.create(source.getClass(), target.getClass(), ignoreNull);
                copierMap.put(key, copier);
            }
        }
        return copier;
    }

    public static <S, T> void convert(S source, T target) {
        convert(source, target, true);
    }

    /**
     * 批量转换
     *
     * @param sourceList  源bean列表
     * @param targetClass 生成的目标list class, 要求bean有无参构造函数
     * @return target bean list
     */
    public static <S, T> List<T> convertList(List<S> sourceList, Class<T> targetClass) {
        if (CollectionUtils.isEmpty(sourceList)) {
            return Collections.emptyList();
        }

        List<T> targetList = new ArrayList<>(sourceList.size());
        for (S source : sourceList) {
            T target = convert(source, targetClass);
            if (target != null) {
                targetList.add(target);
            }
        }
        return targetList;
    }

    /**
     * 将原对象转换为目标类型的对象
     *
     * @param source      源对象
     * @param targetClass 目标对象类型
     * @return 目标对象
     */
    public static <S, T> T convert(S source, Class<T> targetClass) {
        T target = null;
        try {
            target = targetClass.newInstance();
            convert(source, target, false);
        } catch (InstantiationException | IllegalAccessException e) {
            logger.error("bean convert error!", e);
        }
        return target;
    }


    /**
     * bean属性copy
     *
     * @param source     源对象
     * @param target     目标对象
     * @param ignoreNull 是否忽略源对象的null属性,如果忽略,则保留目标对象属性值
     */
    public static <S, T> void convert(S source, T target, boolean ignoreNull) {
        BeanCopier copier = getTheBeanCopier(source, target, ignoreNull);

        // 如果不忽略null值,则不使用Converter
        if (!ignoreNull) {
            copier.copy(source, target, null);
            return;
        }

        // 忽略null值,自定义Converter
        Converter converter = (srcValue, targetClass, context) -> {
            // 如果忽略null值,则取目标对象值
            if (srcValue == null) {
                String property = getPropertyName(targetClass, String.valueOf(context));
                return BeanMap.create(target).get(property);
            }
            // 如果类型不一致,也取目标对象值
            if (!srcValue.getClass().isAssignableFrom(targetClass)) {
                String property = getPropertyName(targetClass, String.valueOf(context));
                return BeanMap.create(target).get(property);
            }
            // 否则取源对象值
            return srcValue;
        };
        copier.copy(source, target, converter);
    }

    /**
     * 返回属性名,setAge ---> age
     *
     * @param target     目标对象属性类型
     * @param methodName 属性对应方法名
     * @return 属性名
     */
    private static String getPropertyName(Class target, String methodName) {
        // 方法前缀长度,默认为3,即'get'的长度
        int prefixLength = 3;

        // 如果是布尔类型,去掉is
        if (Boolean.class.isAssignableFrom(target) && methodName.startsWith("is")) {
            prefixLength = 2;
        }
        // 截取,并将首字母转小写,最终获取属性名
        int fieldNameLength = methodName.length() - prefixLength;
        char[] newChar = new char[fieldNameLength];
        System.arraycopy(methodName.toCharArray(), prefixLength, newChar, 0, fieldNameLength);
        newChar[0] = Character.toLowerCase(newChar[0]);
        return String.valueOf(newChar);
    }

}

主要封装点如下:

  1. BeanCopier初始化相对耗时,这里做了缓存,避免多次初始化;
  2. 自定义Converter,可以忽略null值,以及处理字段类型不一致的问题;