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

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

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 网络机房效果图制作|步骤技术分享[通俗易懂]

    网络机房效果图制作|步骤技术分享[通俗易懂]首先看图,这个图是人视角度,两侧显露出来的空间很大,注重表现的是两侧的机柜,包含列头柜,精密空调及上方的冷通道。第一步:客户肯定得提供图纸类的资料,如CAD图纸,或是手绘的平面布置图等。这里面需要包含机房的数量,排列组合为止或是模块化设置。第二步:拿到资料,和客户沟通之后,首先要明白客户表现的是什么效果,哪个地方是侧重点等。第三步:沟通完了就需要进行下一步,就是如果客户提供了…

  • 1553B总线控制器61580使用

    1553B总线控制器61580使用这里写自定义目录标题收藏一篇关于61580使用的文章,侵删!原文地址:http://emesjx.spaces.eepw.com.cn/articles/article/item/1000231、BU-61580有“缓冲”和“透明”2种存储模式,前者使用BU-61580内部4Kx16bit缓冲区,后者使用外部RAM作为数据缓冲区,最大可达64Kx16bit。2、BU-61580的缓冲模式又…

  • 计算机病毒的活性,计算机病毒的特性

    计算机病毒的活性,计算机病毒的特性计算机病毒一般具有以下特性:1.计算机病毒的程序性(可执行性)计算机病毒与其他合法程序一样,是一段可执行程序,但它不是一个完整的程序,而是寄生在其他可执行程序上,因此它享有一切程序所能得到的权力。在病毒运行时,与合法程序争夺系统的控制权。计算机病毒只有当它在计算机内得以运行时,才具有传染性和破坏性等活性。也就是说计算机CPU的控制权是关键问题。若计算机在正常程序控制下运行,而不运行带病毒的程序,…

  • wenstorm 激活码(最新序列号破解)

    wenstorm 激活码(最新序列号破解),https://javaforall.cn/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

  • 简单搞懂子网划分,学会子网划分这篇就够了(例题详解)

    简单搞懂子网划分,学会子网划分这篇就够了(例题详解)搞定子网划分,网段地址,广播地址,可用地址范围,能够快速了解并学会子网划分!

  • What is Observer and Observable and when we used these?

    What is Observer and Observable and when we used these?

发表回复

您的电子邮箱地址不会被公开。

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