BeanCopier封装

记一次hutool的BeanUtil.copyProperties使用不当导致的异常排查 阅读详情

一、spring的beanutils、hutool的beanutil、cglib的beancopier比较

1、性能:cglib > spring > hutool

2、性能差距:本机4c16g macbookpro,一亿条数据循环,cglib300ms,spring10s,hutool120s

综上所述:cglib性能完爆所有产品

二、痛点:

1、每次使用都需要create,如果上述实验把create放到循环里,结果会变成5s

2、无法实现null字段跳过

三、解决方案(代码见最后)

首先我先说明我的方案参考了网上所有能找到的帖子,包括百度、谷歌,最终参考掘金的以为老哥的(虽然他代码有bug,但是整体思路是参考他的)扩展BeanCopier实现只复制非null值 - 掘金

1、每次使用都需要create,这个很简单,搞个map缓存起来即可,但是你们去网上搜大部分代码都是复制粘贴的,都是key拼接source名字+target名字,v为beancopier对象,这个字符串拼接十分耗时,压测下来对性能影响很大,换成对象存储性能好了很多。

2、无法实现null字段跳过,其实beancopier提供了converter函数式接口给我们拓展,但是他坑就坑在没有目标对象字段,所以无法判断目标字段是否为null,所以只能重写一个converter,重写了converter,beancopier也得跟着重写。所有的重写代码,以及工具类我都贴在最后,方法上都有注释,自己看吧,我所有代码都做过各个维度的单元测试,可以放心食用

public class BeanCopierUtil {
    /**
     * 创建过的BeanCopier实例放到缓存中,下次可以直接获取,提升性能
     */
    private static final Map<CopierIdentity, BeanCopierPlus> BEAN_COPIERS = new ConcurrentHashMap<>();

    /**
     * 该方法没有自定义Converter,简单进行常规属性拷贝
     *
     * @param srcObj  源对象
     * @param destObj 目标对象
     */
    public static void copy(final Object srcObj, final Object destObj) {
        if (Objects.isNull(srcObj) || Objects.isNull(destObj)) {
            throw new RuntimeException("参数为空");
        }

        getCopier(srcObj.getClass(), destObj.getClass(), true).copy(srcObj, destObj, new SkipNullConverter());
    }

    // destClass 必须有无参构造器
    public static <T> T copy(final Object srcObj, final Class<T> destClass) {
        if (Objects.isNull(srcObj) || Objects.isNull(destClass)) {
            throw new RuntimeException("参数为空");
        }

        T t;
        try {
            t = destClass.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        getCopier(srcObj.getClass(), destClass, false).copy(srcObj, t, null);

        return t;
    }

    public static void copyWithNull(final Object srcObj, final Object destObj) {
        if (Objects.isNull(srcObj) || Objects.isNull(destObj)) {
            throw new RuntimeException("参数为空");
        }

        getCopier(srcObj.getClass(), destObj.getClass(), false).copy(srcObj, destObj, null);
    }

    private static BeanCopierPlus getCopier(Class<?> source, Class<?> target, boolean converter) {
        CopierIdentity key = new CopierIdentity(source, target);
        BeanCopierPlus copier;
        if (!BEAN_COPIERS.containsKey(key)) {
            copier = BeanCopierPlus.create(source, target, converter);
            BEAN_COPIERS.put(key, copier);
        } else {
            copier = BEAN_COPIERS.get(key);
        }

        return copier;
    }

    @Data
    @AllArgsConstructor
    private static class CopierIdentity {
        private Class<?> source;
        private Class<?> target;
    }

    private static class SkipNullConverter implements CopyConverter {

        @Override
        public Object convert(Object sourceFiled, Class<?> targetFiledClass, Object targetFiledSetter, Object targetFiled) {
            return sourceFiled == null ? targetFiled : sourceFiled;
        }
    }
}
@FunctionalInterface
public interface CopyConverter {
    /**
     * @param sourceFiled       源属性
     * @param targetFiledClass  目标属性的class
     * @param targetFiledSetter 目标属性的setter方法的方法名
     * @param targetFiled       目标属性
     * @return 设置目标的属性
     */
    Object convert(Object sourceFiled, Class<?> targetFiledClass, Object targetFiledSetter, Object targetFiled);
}
public abstract class BeanCopierPlus {
    private static final BeanCopierPlusKey KEY_FACTORY = (BeanCopierPlusKey) KeyFactory.create(BeanCopierPlusKey.class);
    private static final Type CONVERTER = TypeUtils.parseType(CopyConverter.class.getCanonicalName());
    private static final Type BEAN_COPIER = TypeUtils.parseType(BeanCopierPlus.class.getCanonicalName());
    private static final Signature COPY;
    private static final Signature CONVERT;

    public BeanCopierPlus() {
    }

    public static BeanCopierPlus create(Class source, Class target, boolean useConverter) {
        Generator gen = new Generator();
        gen.setSource(source);
        gen.setTarget(target);
        gen.setUseConverter(useConverter);
        return gen.create();
    }

    public abstract void copy(Object var1, Object var2, CopyConverter var3);

    static {
        // 分别是方法名、返回值,参数类型列表
        COPY = new Signature("copy", Type.VOID_TYPE, new Type[]{Constants.TYPE_OBJECT, Constants.TYPE_OBJECT, CONVERTER});
        CONVERT = TypeUtils.parseSignature("Object convert(Object, Class, Object, Object)");
    }

    public static class Generator extends AbstractClassGenerator {
        private static final Source SOURCE = new Source(BeanCopierPlus.class.getName());
        private Class source;
        private Class target;
        private boolean useConverter;

        public Generator() {
            super(SOURCE);
        }

        public void setSource(Class source) {
            if (!Modifier.isPublic(source.getModifiers())) {
                this.setNamePrefix(source.getName());
            }

            this.source = source;
        }

        public void setTarget(Class target) {
            if (!Modifier.isPublic(target.getModifiers())) {
                this.setNamePrefix(target.getName());
            }

            this.target = target;
        }

        public void setUseConverter(boolean useConverter) {
            this.useConverter = useConverter;
        }

        protected ClassLoader getDefaultClassLoader() {
            return this.source.getClassLoader();
        }

        protected ProtectionDomain getProtectionDomain() {
            return ReflectUtils.getProtectionDomain(this.source);
        }

        public BeanCopierPlus create() {
            Object key = BeanCopierPlus.KEY_FACTORY.newInstance(this.source.getName(), this.target.getName(), this.useConverter);
            return (BeanCopierPlus) super.create(key);
        }

        /**
         * 假设字节码代码如下
         * UserDto var4 = (UserDto)var2;
         * User var5 = (User)var1;
         * Object var10001 = var3.convert(new Integer(var5.getA()), Integer.TYPE, "setA", new Integer(var4.getA()));
         * var4.setA(var10001 == null ? 0 : ((Number)var10001).intValue());
         * var4.setB((String)var3.convert(var5.getB(), CGLIB$load_class$java$2Elang$2EString, "setB", var4.getB()));
         *
         *user为source userdto为target
         */
        public void generateClass(ClassVisitor v) {
            Type sourceType = Type.getType(this.source);
            Type targetType = Type.getType(this.target);
            ClassEmitter ce = new ClassEmitter(v);
            // 获取类
            ce.begin_class(52, 1, this.getClassName(), BeanCopierPlus.BEAN_COPIER, (Type[]) null, "<generated>");
            EmitUtils.null_constructor(ce);
            // 获取copy方法
            CodeEmitter e = ce.begin_method(1, BeanCopierPlus.COPY, null);

            PropertyDescriptor[] sourceGetters = ReflectUtils.getBeanGetters(this.source);
            PropertyDescriptor[] targetSetters = ReflectUtils.getBeanSetters(this.target);

            Map<String, PropertyDescriptor> sourceGetterMap = new HashMap<>();
            for (PropertyDescriptor descriptor : sourceGetters) {
                sourceGetterMap.put(descriptor.getName(), descriptor);
            }

            Local targetLocal = e.make_local();
            Local sourceLocal = e.make_local();
            if (this.useConverter) {
                // 加载copy方法第二个参数,也就是targetObject,对应var2
                e.load_arg(1);
                // 校验,(UserDto)var2
                e.checkcast(targetType);
                // 对应UserDto var4 = (UserDto)var2;
                e.store_local(targetLocal);
                // 加载copy方法第一个参数,也就是sourceObject,对应var1
                e.load_arg(0);
                // 校验,对应(User)var1
                e.checkcast(sourceType);
                // 对应User var5 = (User)var1;
                e.store_local(sourceLocal);
            } else {
                e.load_arg(1);
                e.checkcast(targetType);
                e.load_arg(0);
                e.checkcast(sourceType);
            }

            for (PropertyDescriptor targetSetter : targetSetters) {
                PropertyDescriptor sourceGetter = sourceGetterMap.get(targetSetter.getName());

                if (sourceGetter != null) {
                    MethodInfo sourceRead = ReflectUtils.getMethodInfo(sourceGetter.getReadMethod());
                    MethodInfo sourceWrite = ReflectUtils.getMethodInfo(sourceGetter.getWriteMethod());
                    MethodInfo targetRead = ReflectUtils.getMethodInfo(targetSetter.getReadMethod());
                    MethodInfo targetWrite = ReflectUtils.getMethodInfo(targetSetter.getWriteMethod());

                    // ps字节码变成没有花括号,所以有时候觉得加载有点怪
                    if (this.useConverter) {
                        // 获取源字段类型
                        Type sourceFieldType = sourceWrite.getSignature().getArgumentTypes()[0];
                        // 获取目标字段类型
                        Type targetFieldType = targetWrite.getSignature().getArgumentTypes()[0];
                        if (!sourceFieldType.getClassName().equals(targetFieldType.getClassName())) continue;

                        // 加载局部变量,对应var4
                        e.load_local(targetLocal);
                        // 加载copy方法第三个参数,也就是converter,对应var3
                        e.load_arg(2);
                        // 加载局部变量,对应var5
                        e.load_local(sourceLocal);
                        // 对应var5.getA()
                        e.invoke(sourceRead);
                        // 装箱,对应new Integer(var5.getA())
                        e.box(sourceRead.getSignature().getReturnType());
                        // 对应Integer.TYPE
                        EmitUtils.load_class(e, targetFieldType);
                        // 对应"setA"
                        e.push(targetWrite.getSignature().getName());

                        // 加载局部变量,对应ar4
                        e.load_local(targetLocal);
                        // 对应var4.getA()
                        e.invoke(targetRead);
                        // 装箱,对应new Integer(var4.getA())
                        e.box(targetRead.getSignature().getReturnType());

                        // 执行converter方法
                        e.invoke_interface(BeanCopierPlus.CONVERTER, BeanCopierPlus.CONVERT);
                        // 拆箱及null赋0,对应var10001 == null ? 0 : ((Number)var10001).intValue()
                        e.unbox_or_zero(targetFieldType);
                        // 执行target的set方法,对应var4.setA
                        e.invoke(targetWrite);
                    } else if (compatible(sourceGetter, targetSetter)) {
                        e.dup2();
                        e.invoke(sourceRead);
                        e.invoke(targetWrite);
                    }
                }
            }

            e.return_value();
            e.end_method();
            ce.end_class();
        }

        private static boolean compatible(PropertyDescriptor getter, PropertyDescriptor setter) {
            return setter.getPropertyType().isAssignableFrom(getter.getPropertyType());
        }

        protected Object firstInstance(Class type) {
            return ReflectUtils.newInstance(type);
        }

        protected Object nextInstance(Object instance) {
            return instance;
        }
    }

    interface BeanCopierPlusKey {
        Object newInstance(String var1, String var2, boolean var3);
    }
}

如果你能看到这,那么就听我唠叨完

1、如何编写字节码代码?

说实话我第一眼看到beancopier代码时候根本看不懂,看了一下午,其实在你在测试方法的第一行加上System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/xxx/Desktop/test"); 就可以把字节码生成的class文件输出到指定目录,然后对照这class文件反编译结果来写调整字节码代码,会简单很多。

2、为啥我要整这个?

①以前我做一个项目qps单台到3000就上不去,瓶颈在cpu,但是我接口非常简单,tomcat也调优过了,最后问题出在我用了spring的beanutils,所以从那时候开始我就很排斥用这个。

②程序员又叫做软件工程师,工程师就应该具有工匠精神,我知道直接用spring的很方便,也没啥问题,但是我觉得作为一个合格程序员,要心怀工匠之心,眼望星辰大海,甚至带点强迫心理。

最后吐槽一下:国内博客真鸡儿垃圾,到处都是复制粘贴。吐了。

BeanCopier拷贝对象中List类型属性的思考 背景 最近开发接口过程中,发现在使用BeanCopier拷贝对象的时候,当对象中嵌套自定义对象类型属性的时候,如果对象名称一致,但是对象类型不一致的时候,拷贝的时候,该属性是会被忽略的,但是当对象中嵌套List集合类型,集合中的类型是不同的对象类型,使用BeanCopier拷贝之后,返回给前台的数据是正确的,感觉不太懂其中的原理,就测试了下。 测试过程 新建几个对象 @Data @NoArgs... 阅读详情

相关推荐

一个封装BeanCopier工具类

工具类BeanCopierUtils 1. 支持source对象到target对象的拷贝 2. 支持List<S> source到 List<T> target的拷贝

心有猛虎 细嗅蔷薇 882

BeanCopier的使用

【代码】BeanCopier的使用。

weixin_43860634的博客 3992

光纤布拉格光栅(FBG)笔记【1】:波导结构和布拉格波长推导

引言 本文是光纤布拉格光栅(FBG)的笔记【1】,主要介绍了光纤布拉格光栅的波导结构,并且利用动量和能量守恒推导了光纤布拉格波长的表达式 普通光纤中让折射率发生周期性变化就是结构最简单的均匀光纤布拉格光栅(FBG),其波导结构如下图所示。 其传感原理描述可以分为以下的三点: ①光纤纤芯传播的光会在每个光栅面进行反射和透射; ②如果不满足布拉格条件,依次排列的光栅平面反射的光会逐渐变得不同直到最后相互抵消;同时由于系数不匹配,与布拉格波长不相符的光在每个光栅平面的反射也很微弱,这些光大部分在光纤中发生透.

subtitle_的博客 2万+

Spring学习笔记(二)【CGLIB浅拷贝BeanCopier的使用和详解】

常用的bean拷贝工具类当中,主要有Apache提供的beanUtils、Spring提供的beanUtils、Cglib提供的beanCopier,性能上分析如下表所示

Vinjcent 7487

封装BeanCopier工具,支持单个对象以及列表拷贝,超高性能简单实用

说明 前几天写了一个关于Spring BeanUtils的封装类代码片段六:BeanUtils.copyProperties扩展复制列表,然后又发现了一款性能更好的工具BeanCopier,这款工具性能比较好,但是易用性稍差,故而封装一下,分享出来。由于使用上和上面????那个基本一致,故不作过多介绍。至于性能提升多少,小伙伴们可以参考各类对象属性拷贝工具性能测试对比(BeanCopier、BeanUt...

学而时习之 3944

关于我封装BeanCopier这件事

Bean的拷贝我们不陌生,经常与之打交道,看了很多评测,抛开功能不说,性能上前三大致就是原生的get/set、MapStruct、cglib bean copy。之所以需要是用beanCopy,就是因为大量的get/set不雅观(绝对不是我们懒)。MapStruct是在编译时期生成的代码,但是其使用让我觉得繁琐。cglib使用动态字节码技术生成class文件,在全局缓存的copier之后速度也是非常的快。但是也有很多不方便的地方,我结合实际需求,对它动了心❤。...

jsl575的博客 1353

基于BeanCopier封装的一个简易类型转换器

基于BeanCopier封装的简易类型转换器 import org.apache.commons.collections.CollectionUtils; import org.springframework.cglib.beans.BeanCopier; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class BaseConvertUtils<T,S> {

luckyxs的博客 620

使用CGlib实现Bean拷贝(BeanCopier)

目录 BeanCopier基本用法 自定义转换器 封装BeanCopier   在做业务的时候,我们有时为了隔离变化,会将DAO查询出来的Entity,和对外提供的DTO隔离开来。大概90%的时候,它们的结构都是类似的,但是我们很不喜欢写很多冗长的b.setF1(a.getF1())这样的代码,于是我们需要BeanCopier来帮助我们。选择Cglib的BeanCopier进行Bean...

bingguang1993的博客 1838

BeanCopier的使用快捷复制封装实体类

BeanCopier 使用 1.创建实例 BeanCopier mbCopier = BeanCopier.create(WxWlLlmb.class, WxWlLlmb.class, false); 2.copy操作 mbCopier .copy(person1, person2, null);

weixin_42671581的博客 393

Cglib BeanCopier工具类

目录 前言 上代码 解析 github地址 前言 一般大家都会使用BeanUtils.copyProperties来复制对象,不过Cglib更快 所以今晚熬夜把这个工具类也封装一下,由于在创建BeanCopier会消耗很多时间,我们使用Map来储存BeanCopier 上代码 import com.example.demo.entity.User; import com.exa...

大鸡腿的博客 1580

使用Spring的BeanCopier进行VO、PO、DTO对象转换

使用Spring的BeanCopier进行VO、PO、DTO对象转换 一、不封装工具类 实体类User、UserVO package com.example.demokafka.entity; import java.util.Date; import java.io.Serializable; import lombok.Data; /** * 用户表 (User)实体类 * * @author izhouy * @since 2021-06-29 13:04:15 */ @Data

weixin_43979189的博客 1803

import org.springframework.cglib.beans.BeanCopier;使用BeanCopier.create去复制list数据代码实例

import org.springframework.cglib.beans.BeanCopier; 使用BeanCopier.create去复制list数据代码实例

gb4215287的博客 1039

比copyProperties(Object source, Object target)效率高的拷贝方式BeanCopier

/** @Description: bean封装类,默认转换器,字段相同属性相同copy @author: yee */ public class WrapperBeanCopier { private WrapperBeanCopier() {} private static final Map<String, BeanCopier> BEAN_COPIER_...

JewelYee的博客 387

用 cglib 的 BeanCopier 替换其他工具的 BeanUtils 来减少你的 Bean 拷贝时间

Bean 拷贝的工具有很多,有 Apache BeanUtils、Spring BeanUtils、Mapstruct、cglib BeanCopier 等等Apache 和 Spring 的 BeanUtils 效率并不是我想要的,而使用 Mapstruct 又太繁琐了那有没有简单易用的,而且效率还比较高的呢?这就是我这篇文章要讲述的主要内容。

Eighteen_y的博客 625

对象拷贝类PropertyUtils,BeanUtils,BeanCopier的技术沉淀

对象拷贝的应用现状简介: 业务系统中经常需要两个对象进行属性的拷贝,不能否认逐个的对象拷贝是最快速最安全的做法,但是当数据对象的属性字段数量超过程序员的容忍的程度,代码因此变得臃肿不堪,使用一些方便的对象拷贝工具类将是很好的选择。 目前流行的较为公用认可的工具类: Apache的两个版本:(反射机制) org.apache.commons.beanutils.Propert

不断成长的我 2234
上一篇: java令牌桶
下一篇: 自定义线程池(一)futuretask版本
Old Wang
博客等级 码龄10年 15粉丝 30原创
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值