自定义java对象转换工具类

背景

项目中经常有VO.PO.DTO等之间转换,由于apache工具类中BeanUtils.copyProperties及Json序列化反序列化方式转换性能比较低(阿里巴巴规范检查有提示不建议采用)。
而org.springframework.beans中的BeanUtils.copyProperties性能好,但不支持自定义类型转换,功能较弱。

于是本人综合各转换特点扩展出来一个工具类,让类型转换性能得到提升,还支持自定义各字段类型的处理方式,并且支持每个转换后回调方法.

  • 支持List列表转换
  • 支持回调
  • 支持Supplier传值
  • 该工具类比apach beanutils快,比springframework的BeanUtil.copyProperties强大
  • 支持自定义类型转换 默认加入常用date与localDate及LocalDatetime转换
  • 默认支持数据库Clob类型与字符串转换
  • 支持Map与类对象转换
  • 支持下划线转驼峰
  • 支持简单嵌套转换

工具类代码

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.BeanUtils;
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.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Supplier;


/**
 * @author renxiaodong
 */
public class BeanHelper {

    static {
        rules = new HashMap<>();
        registerConverter(new DefaultDateConverter(), Date.class);
        registerConverter(new DefaultLocalDateConverter(), LocalDate.class);
        registerConverter(new DefaultLocalDateTimeConverter(), LocalDateTime.class);
        registerConverter(new DefaultClobConverter(), Clob.class);
    }

    static Map<Class, Converter> rules;

    public static void registerConverter(Converter converter, Class cls) {
        rules.put(cls, converter);
    }

    public static void deregisterConverter(Converter converter, Class cls) {
        rules.remove(cls, converter);
    }

    public static Converter getConverter(Class cls) {
        Converter converter = rules.get(cls);
        if (converter == null) {
            for (Class keyClz : rules.keySet()) {
                if (keyClz.isAssignableFrom(cls)) {
                    converter = rules.get(keyClz);
                    rules.put(cls, converter);
                    break;
                }
            }
        }
        return converter;
    }


    public static class DefaultDateConverter implements Converter {
        SimpleDateFormat dateTimeFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        @Override
        public <T> T convert(Class<T> targetClass, Object sourceValue) {
            if (sourceValue == null) {
                return null;
            }

            if (targetClass == Date.class) {
                return (T) sourceValue;
            } else if (targetClass == LocalDate.class) {
                Date d = (Date) sourceValue;
                return (T) LocalDate.of(d.getYear(), d.getMonth(), d.getDate());
            } else if (targetClass == LocalDateTime.class) {
                Date d = (Date) sourceValue;
                return (T) LocalDateTime.ofInstant(d.toInstant(), ZoneId.systemDefault());
            } else if (targetClass == String.class) {
                return (T) dateTimeFormatter.format((Date) sourceValue);
            }
            return null;
        }
    }

    public static class DefaultLocalDateConverter implements Converter {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        @Override
        public <T> T convert(Class<T> targetClass, Object sourceValue) {
            if (sourceValue == null) {
                return null;
            }
            LocalDate localDate = (LocalDate) sourceValue;
            if (targetClass == Date.class) {
                return (T) new Date(localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth());
            } else if (targetClass == LocalDate.class) {
                return (T) sourceValue;
            } else if (targetClass == LocalDateTime.class) {
                return (T) LocalDateTime.of(localDate.getYear(), localDate.getMonth(), localDate.getDayOfMonth(), 0, 0);
            } else if (targetClass == String.class) {
                return (T) localDate.format(dateTimeFormatter);
            }
            return null;
        }
    }

    public static class DefaultLocalDateTimeConverter implements Converter {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        @Override
        public <T> T convert(Class<T> targetClass, Object sourceValue) {
            if (sourceValue == null) {
                return null;
            }
            LocalDateTime localDate = (LocalDateTime) sourceValue;
            if (targetClass == Date.class) {
                return (T) Date.from(localDate.atZone(ZoneId.systemDefault()).toInstant());
            } else if (targetClass == LocalDate.class) {
                return (T) LocalDate.of(localDate.getYear(), localDate.getMonth(), localDate.getDayOfMonth());
            } else if (targetClass == LocalDateTime.class) {
                return (T) sourceValue;
            } else if (targetClass == String.class) {
                return (T) localDate.format(dateTimeFormatter);
            }
            return null;
        }
    }

    public static class DefaultClobConverter implements Converter {


        @Override
        public <T> T convert(Class<T> targetClass, Object sourceValue) {
            if (sourceValue == null || !(sourceValue instanceof Clob)) {
                return null;
            }
            try {
                Clob clob = (Clob) sourceValue;
                if (targetClass == String.class) {

                    Reader reader = clob.getCharacterStream();

                    StringBuilder buf = new StringBuilder();

                    try {
                        char[] chars = new char[2048];
                        for (; ; ) {
                            int len = reader.read(chars, 0, chars.length);
                            if (len < 0) {
                                break;
                            }
                            buf.append(chars, 0, len);
                        }
                    } catch (Exception ex) {
                        throw new JSONException("read string from reader error", ex);
                    }

                    String text = buf.toString();
                    reader.close();

                    return (T) text;
                }
                return null;
            } catch (Exception e) {
                throw new RuntimeException("转换 clob 失败", e);
            }
        }
    }

    /**
     * BeanHelper.copyProperties(xxEntity,XXVO.class)
     * 建议采用BeanHelper.copyProperties(xxEntity,XXVO::new)  这种方式性能更好
     *
     * @param source
     * @param clazz
     * @param <T>
     * @param <S>
     * @return
     */
    public static <S, T> T copyProperties(S source, Class<T> clazz, String... ignoreProperties) {
        T o = null;
        if (source instanceof List) {
            return (T) copyListProperties((List) source, clazz, ignoreProperties);
        } else if (source instanceof Map) {
            return (T) copyProperties((Map) source, clazz, true, ignoreProperties);
        }
        try {
            o = clazz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("无法实例化" + clazz + "类型");
        }
        return copyProperties(source, o, ignoreProperties);

    }

    /**
     * 推荐使用该方法
     * BeanHelper.copyProperties(xxEntity,XXVO::new)
     *
     * @param source
     * @param target
     * @param ignoreProperties
     * @param <T>
     * @return
     */
    public static <T, S> T copyProperties(S source, Supplier<T> target, String... ignoreProperties) {
        if (source instanceof List) {
            return (T) copyListProperties((List) source, target, ignoreProperties);
        } else if (source instanceof Map) {
            return (T) copyProperties((Map) source, target, ignoreProperties);
        } else {
            return copyProperties(source, target.get(), ignoreProperties);
        }
    }

    public static <S, T> T copyProperties(S source, T target, String... ignoreProperties) {
        if (source == null) {
            return null;
        }
        Class<?> actualEditable = target.getClass();

        PropertyDescriptor[] targetPds = BeanUtils.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 = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getName());
                if (sourcePd != null) {
//                    if (Collection.class.isAssignableFrom(targetPd.getPropertyType()) && Collection.class.isAssignableFrom(sourcePd.getPropertyType())) {
                        //    Collection children = (Collection) sourcePd.getReadMethod().invoke(source);
//                        for (Object val : children) {
//                            copyProperties( source, )
//                        }
//                        continue;
 //                       System.out.println("不支持属性为List的类型转换");
 //                   }
                    Method readMethod = sourcePd.getReadMethod();
                    if (readMethod != null &&
                            (ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType()) || rules.containsKey(readMethod.getReturnType()))) {
                        try {
                            if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                                readMethod.setAccessible(true);
                            }
                            Object value = readMethod.invoke(source);
                            if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                                writeMethod.setAccessible(true);
                            }
                            Converter converter = getConverter(sourcePd.getPropertyType());
                            if (converter != null) {
                                value = converter.convert(targetPd.getPropertyType(), value);
                            }

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


    /**
     * 这个方法效率极低,不要使用 ,建议使用 BeanHelper.copyListProperties
     *
     * @param obj
     * @param clazz
     * @param <T>
     * @param <Y>
     * @return
     */
    @Deprecated()
    public static <T, Y> List<T> convertList(List<Y> obj, Class<T> clazz) {
        if (obj == null) {
            return null;
        }
        List<T> resultList = JSON.parseArray(JSON.toJSONString(obj), clazz);
        return resultList;
    }

    public static <T> Page<T> toPage(IPage<?> pageList, Class<T> clazz) {
        if (pageList == null) {
            return null;
        }

        Page<T> destPage = new Page<>();
        destPage.setPages(pageList.getPages());
        destPage.setSize(pageList.getSize());
        destPage.setCurrent(pageList.getCurrent());
        destPage.setTotal(pageList.getTotal());
        List<T> destList = copyListProperties(pageList.getRecords(), clazz);
        destPage.setRecords(destList);
        return destPage;
    }

    public static <S, T> List<T> copyListProperties(List<S> sources, Class<T> cls, String... ignoreProperties) {
        return copyListProperties(sources, cls, null, ignoreProperties);
    }

    public static <S, T> List<T> copyListProperties(List<S> sources, Class<T> cls, ColaBeanUtilsCallBack<S, T> callBack, String... ignoreProperties) {
        List<T> list = new ArrayList<>(sources.size());
        if (!CollectionUtils.isEmpty(sources) && sources.get(0) instanceof Map) {

            for (S m : sources) {
                try {
                    T t = copyProperties((Map<String, Object>) m, cls, true, ignoreProperties);

                    if (callBack != null) {
                        // 回调
                        callBack.callBack(m, t);
                    }
                    list.add(t);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return list;
        }
        for (S source : sources) {
            T t = null;
            try {
                t = cls.newInstance();
                copyProperties(source, t, ignoreProperties);
                if (callBack != null) {
                    // 回调
                    callBack.callBack(source, t);
                }
                list.add(t);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return list;
    }

    public static <S, T> List<T> copyListProperties(List<S> sources, Supplier<T> supplier, String... ignoreProperties) {

        return copyListProperties(sources, supplier, null, ignoreProperties);
    }

    /**
     * @author Johnson
     * 使用场景:Entity、Bo、Vo层数据的复制,因为BeanUtils.copyProperties只能给目标对象的属性赋值,却不能在List集合下循环赋值,因此添加该方法
     * 如:List<AdminEntity> 赋值到 List<AdminVo> ,List<AdminVo>中的 AdminVo 属性都会被赋予到值
     * S: 数据源类 ,T: 目标类::new(eg: AdminVo::new)
     */
    public static <S, T> List<T> copyListProperties(List<S> sources, Supplier<T> supplier, ColaBeanUtilsCallBack<S, T> callBack, String... ignoreProperties) {
        List<T> list = new ArrayList<>(sources.size());
        if (!CollectionUtils.isEmpty(sources) && sources.get(0) instanceof Map) {

            for (S m : sources) {
                T t = copyProperties((Map<String, Object>) m, supplier, true);
                if (callBack != null) {
                    // 回调
                    callBack.callBack(m, t);
                }
                list.add(t);
            }
            return list;
        } else {
            for (S source : sources) {
                T t = copyProperties(source, supplier, ignoreProperties);
                if (callBack != null) {
                    // 回调
                    callBack.callBack(source, t);
                }
                list.add(t);
            }
            return list;
        }
    }

    public static <M> M copyProperties(Map<String, Object> value, Supplier<M> supplier, String... ignoreField) {
        return copyProperties(value, supplier, true, ignoreField);
    }

    /**
     * @param value
     * @param supplier
     * @param cemalCase 是否下划线转cemalCase
     * @param <M>
     * @return
     */
    public static <M> M copyProperties(Map<String, Object> value, Supplier<M> supplier, boolean cemalCase, String... ignoreField) {
        M t = supplier.get();
        return copyProperties(value, supplier.get(), cemalCase);
    }

    public static <M> M copyProperties(Map<String, Object> value, Class<M> clz, String... ignoreField) {

        return copyProperties(value, clz, true, ignoreField);
    }

    public static <M> M copyProperties(Map<String, Object> value, Class<M> clz, boolean cemalCase, String... ignoreField) {
        M t = null;
        try {
            t = clz.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new FatalBeanException("对象创建失败", e);
        }
        return copyProperties(value, t, cemalCase);
    }

    public static <M> M copyProperties(Map<String, Object> value, M target, boolean cemalCase, String... ignoreField) {
        M t = target;
        List<String> ignoreList = Arrays.asList(ignoreField);
        PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(t.getClass());

        Map copy = new HashMap(value.size());
        if (cemalCase) {
            value.forEach((key, val) -> {
                if (ignoreList.contains(key)) {
                } else {
                    key = key.replaceAll("_", "").replaceAll("-", "").toUpperCase();
                    copy.put(key, val);
                }
            });

        } else {
            copy.putAll(value);
        }
        for (PropertyDescriptor targetPd : targetPds) {
            if (targetPd != null) {
                try {
                    Object o = copy.get(targetPd.getName().toUpperCase(Locale.ROOT));
                    if (null == o) {
                        continue;
                    }
                    Converter converter = getConverter(o.getClass());
                    if (converter != null) {
                        o = converter.convert(targetPd.getPropertyType(), o);
                    }
                    Method writeMethod = targetPd.getWriteMethod();
                    if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
                        writeMethod.setAccessible(true);
                    }
                    writeMethod.invoke(t, ConverterSupport.cast(o, targetPd.getPropertyType()));
                } catch (Exception e) {
                    throw new FatalBeanException(
                            "Could not copy property '" + targetPd.getName() + "' from source to target", e);
                }
            }
        }
        return t;
    }

然后给一段性能对比测试

//单元测试
public class BeanHelperTest {

    @Test
    public void convertCompare() {
        List<TestVo> mockList = new ArrayList<>(100000);
        List<Map<String, Object>> mapList = new ArrayList<>(100000);

        for (int i = 0; i < 100000; i++) {
            Map<String, Object> other = new HashMap<>();
            other.put("sex", "female");
            other.put("location", "asian");

            List<ChildVo> children=new ArrayList<>(3);
            for (int j = 0; j < 3;j++){
                ChildVo vo = new ChildVo();
                vo.setAge(j);
                vo.setBirthday(LocalDateTime.now());
                vo.setName(UUIDGenerator.nextId());
                vo.setBirthYear(2022L);
                vo.setProperties(other);
                children.add(vo);
            }
            TestVo vo = new TestVo();
            vo.setAge(Math.round(0));
            vo.setBirthday(LocalDateTime.now());
            vo.setName(UUIDGenerator.nextId());
            vo.setBirthYear(2022L);
            vo.setProperties(other);
            vo.setChildren(children);
            mockList.add(vo);

            Map<String, Object> map = new HashMap<>();
            map.put("age", vo.getAge());
            map.put("birthday", vo.getBirthday());
            map.put("name", vo.getName());
            map.put("birth_year", 2020);
            map.put("properties", other);
            mapList.add(map);
        }
        convertListAsJson(mockList);
        testCopyListProperties(mockList);
        testCopyListPropertiesWithSupplier(mockList);
        testCopyMapListProperties(mapList);
    }


    public void convertListAsJson(List<TestVo> mockList) {

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        BeanHelper.convertList(mockList, TestPo.class);

        stopWatch.stop();

        System.out.printf("10万数据BeanHelper.convertList执行时长:%d 毫秒.%n", stopWatch.getTime(TimeUnit.MILLISECONDS));
    }


public void testCopyListProperties(List<TestVo> mockList) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        BeanHelper.copyListProperties(mockList, TestPo.class);
        stopWatch.stop();

        System.out.printf("10万数据BeanHelper.copyListProperties执行时长:%d 毫秒.%n", stopWatch.getTime(TimeUnit.MILLISECONDS));
    }

    public void testCopyListPropertiesWithSupplier(List<TestVo> mockList) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        try {
            List<TestPo> testPos = BeanHelper.copyListProperties(mockList, TestPo::new); 
        } catch (Exception e) {
            e.printStackTrace();
        }
        stopWatch.stop();
        System.out.printf("10万数据BeanHelper.copyListProperties Supplier方式执行时长:%d 毫秒.%n", stopWatch.getTime(TimeUnit.MILLISECONDS));
    }

    public void testCopyMapListProperties(List<Map<String, Object>> mapList) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        try {
            List<TestPo> testPos = BeanHelper.copyListProperties( mapList, TestPo::new);
            System.out.println(testPos.get(0));
        } catch (Exception e) {
            e.printStackTrace();
        }
        stopWatch.stop();
        System.out.printf("10万数据BeanHelper.copyListProperties Map转换方式执行时长:%d 毫秒.%n", stopWatch.getTime(TimeUnit.MILLISECONDS));
    }

综合比对效率

10万数据fastJson JSON.parseArray(JSON.toJSONString(obj), clazz)序列化再返序列化执行时长:3552毫秒.
10万数据org.apache.commons.beanutils.BeanUtils.copyProperties执行时长:2686毫秒.
10万数据自定义BeanHelper.copyListProperties Supplier方式执行时长:220毫秒. 
10万数据自定义BeanHelper.copyListProperties Map转换方式执行时长:907毫秒. 

完整代码浏览kuizii脚手架-基于spring cloud

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值