beanCopier_cerdip封装

beanCopier_cerdip封装BeanCopier封装

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

一、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的很方便,也没啥问题,但是我觉得作为一个合格程序员,要心怀工匠之心,眼望星辰大海,甚至带点强迫心理。

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/197021.html原文链接:https://javaforall.net

(0)
上一篇 2025年9月15日 下午3:22
下一篇 2025年9月15日 下午4:01


相关推荐

  • cs架构嵌入bs_cs架构与bs架构的对比

    cs架构嵌入bs_cs架构与bs架构的对比主要区别:Client/Server是建立在局域网的基础上的.Browser/Server是建立在广域网的基础上的.1.硬件环境不同C/S一般建立在专用的网络上,小范围里的网络环境,局域网之间再通过专门服务器提供连接和数据交换服务.B/S建立在广域网之上的,不必是专门的网络硬件环境,例如电话上网,租用设备.信息管理.有比C/S更强的适应范围,一般只要有操作系统和浏览器就行2.对…

    2025年9月16日
    7
  • ireport使用教程视频_proe拖动图形

    ireport使用教程视频_proe拖动图形iReport使用教程【原创】iReport与JasperReport简介1.1  简介JasperReport是报表的引擎部分,界面设计是用iReport。为什么选择这两个软件呢?因为这两个软件都是开源的,即免费的(虽然某些文档收费,但是磨灭不了我们使用它的理由)。JasperReport是一个报表制作程序,用户按照它制定的规则编写一个XML文件,然后得到用户需要输出的

    2025年10月23日
    5
  • python从字符串中提取数字

    1、使用正则表达式,用法如下:##总结##^匹配字符串的开始。##$匹配字符串的结尾。##\b匹配一个单词的边界。##\d匹配任意数字。##\D匹配任意非数字字符。##x?匹配一个可选的x字符(换言之,它匹配1次或者0次x字符)。##x*匹配0次或者多次x字符。##x+匹配1次或者多次x字符。##x{n,m}匹配…

    2022年4月3日
    178
  • oracle 游标 定义,Oracle游标

    oracle 游标 定义,Oracle游标2019 05 13 游标 cursor 能够根据查询条件从数据表中提取一组记录 将其作为一个临时表置于数据缓冲区中 利用指针逐行对记录数据进行操作 隐式游标在执行 SQL 语句时 Oracle 会自动创建隐式游标 该游标是内存中处理该语句的数据缓冲区 存储了执行 SQL 语句的结果 通过隐式游标属性可获知 SQL 语句的执行状态信息 found 布尔型属性 如果 sql 语句至少影响到一行数据 值为 tru

    2026年3月20日
    2
  • opc服务器配置PLC信号,plc配置OPC服务器

    opc服务器配置PLC信号,plc配置OPC服务器plc配置OPC服务器内容精选换一换云耀云服务器(HyperElasticCloudServer,HECS)是可以快速搭建简单应用的新一代云服务器,具备独立、完整的操作系统和网络功能。提供快速应用部署和简易的管理能力,适用于网站搭建、开发环境等低负载应用场景。具有高性价比、易开通、易搭建、易管理的特点。云耀云服务器与弹性云服务器的主要区别:云耀云服务器:云耀云服务器是精简视图提供了云服务器…

    2022年6月20日
    26
  • 这11款chrome神器,用起来爽到爆

    这11款chrome神器,用起来爽到爆前言对于从事IT行业的我们来说,几乎无时无刻都在用chrome浏览器,因为它给我们的工作和生活带来了极大的便利。今天给大家分享我用过的11款牛逼的chrome插件,你看完前3个可能就会忍不住想点赞了。1.谷歌翻译很多小伙伴,英语不太好,包括我自己,英语刚过四级。从事软件相关工作时,有时有些吃力,因为很多优秀的技术网站、书籍或者文章都是老外写的,如果因为看不懂就放弃阅读,我们将会少了很多学习和进步的机会。今天分享的第一个神器就是:谷歌翻译。在没使用谷歌翻译之前,访问https://doc

    2022年5月6日
    53

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号