一、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
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 copy(final Object srcObj, final Class
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, "
"); 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
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/206926.html原文链接:https://javaforall.net
