深圳网络专科网站建设,宿迁做网站建设的公司,h5制作软件免费手机版下载,恒大房地产最新消息收录于热门专栏Java基础教程系列#xff08;进阶篇#xff09;
在实际的项目开发中#xff0c;对象间赋值普遍存在#xff0c;随着双十一、秒杀等电商过程愈加复杂#xff0c;数据量也在不断攀升#xff0c;效率问题#xff0c;浮出水面。
问#xff1a;如果是你来写…收录于热门专栏Java基础教程系列进阶篇
在实际的项目开发中对象间赋值普遍存在随着双十一、秒杀等电商过程愈加复杂数据量也在不断攀升效率问题浮出水面。
问如果是你来写对象间赋值的代码你会怎么做 答想都不用想直接代码走起来get、set即可。
问下图这样 答对啊你怎么能把我的代码放到网上
问没我只是举个例子
答这涉及到商业机密是很严重的问题
问我发现你挺能扯皮啊直接回答问题行吗
答OKOK我也觉得这样写很low上次这么写之后差点挨打 对象太多ctrl c strl v键盘差点没敲坏 而且很容易出错一不留神属性没对应上赋错值了 代码看起来很傻缺一个类好几千行全是get、set复制还起个了自以为很优雅的名字transfer 如果属性名不能见名知意还得加上每个属性的含义注释基本这种赋值操作都是要加的注释很重要注释很重要注释很重要 代码维护起来很麻烦 如果对象过多会产生类爆炸问题如果属性过多会严重违背阿里巴巴代码规约一个方法的实际代码最多20行 问行了行了说说怎么解决吧。
答很简单啊可以通过工具类Beanutils直接赋值啊
问我听说工具类最近很卷你用的哪个啊 答就Apache自带的那个啊贼简单。我手写一个给你欣赏一下。 问你这代码报错啊避免用Apache Beanutils进行属性的copy。
答没报错只是严重警告而已代码能跑就行有问题再优化呗
问你这什么态度人事在哪划拉的人为啥会出现严重警告 答拿多少钱干多少活我又不是XXX应该是性能问题吧
问具体什么原因导致的呢
答3000块钱还得手撕一下 apache copyProperties的源代码呗
通过单例模式调用copyProperties但是每一个方法对应一个BeanUtilsBean.getInstance()实例每一个类实例对应一个实例这不算一个真正的单例模式。
public static void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {BeanUtilsBean.getInstance().copyProperties(dest, orig);
}性能瓶颈 -- 日志太多也是病 通过源码可以看到每一个copyProperties都要进行多次类型检查还要打印日志。
/*** org.apache.commons.beanutils.BeanUtils.copyProperties方法源码解析*/
public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException {// 类型检查if (dest null) {throw new IllegalArgumentException(No destination bean specified);} else if (orig null) {throw new IllegalArgumentException(No origin bean specified);} else {// 打印日志if (this.log.isDebugEnabled()) {this.log.debug(BeanUtils.copyProperties( dest , orig ));}int var5;int var6;String name;Object value;// 类型检查// DanyBean 提供了可以动态修改实现他的类的属性名称、属性值、属性类型的功能if (orig instanceof DynaBean) {// 获取源对象所有属性DynaProperty[] origDescriptors ((DynaBean)orig).getDynaClass().getDynaProperties();DynaProperty[] var4 origDescriptors;var5 origDescriptors.length;for(var6 0; var6 var5; var6) {DynaProperty origDescriptor var4[var6];// 获取源对象属性名name origDescriptor.getName();// 判断源对象是否可读、判断目标对象是否可写if (this.getPropertyUtils().isReadable(orig, name) this.getPropertyUtils().isWriteable(dest, name)) {// 获取对应的值value ((DynaBean)orig).get(name);// 每个属性都调用一次copyPropertythis.copyProperty(dest, name, value);}}} else if (orig instanceof Map) {MapString, Object propMap (Map)orig;Iterator var13 propMap.entrySet().iterator();while(var13.hasNext()) {Map.EntryString, Object entry (Map.Entry)var13.next();String name (String)entry.getKey();if (this.getPropertyUtils().isWriteable(dest, name)) {this.copyProperty(dest, name, entry.getValue());}}} else {PropertyDescriptor[] origDescriptors this.getPropertyUtils().getPropertyDescriptors(orig);PropertyDescriptor[] var14 origDescriptors;var5 origDescriptors.length;for(var6 0; var6 var5; var6) {PropertyDescriptor origDescriptor var14[var6];name origDescriptor.getName();if (!class.equals(name) this.getPropertyUtils().isReadable(orig, name) this.getPropertyUtils().isWriteable(dest, name)) {try {value this.getPropertyUtils().getSimpleProperty(orig, name);this.copyProperty(dest, name, value);} catch (NoSuchMethodException var10) {}}}}}
}通过 jvisualvm.exe 检测代码性能 再通过jvisualvm.exe检测一下运行情况果然logging.log4j赫然在列稳居耗时Top1。
问还有其它好的方式吗性能好一点的 答当然有据我了解有 4 种工具类实际上可能会有更多话不多说先简单介绍一下。 org.apache.commons.beanutils.BeanUtils; org.apache.commons.beanutils.PropertyUtils; org.springframework.cglib.beans.BeanCopier; org.springframework.beans.BeanUtils 问那你怎么不用 答OK我来演示一下
public class Test {public static void main(String[] args) {User user new User();user.setUserId(1);user.setUserName(测试编程);user.setCardId(123);user.setCreateTime(2023-01-03);user.setEmail(666666666qq.com);user.setOperate(测试);user.setOrgId(46987916);user.setPassword(123456);user.setPhone(10086);user.setRemark(456);user.setSex(1);user.setStatus(1);user.setTel(110);user.setType(0);user.setUpdateTime(2023-01-05);User target new User();int sum 10000000;apacheBeanUtilsCopyTest(user,target,sum);commonsPropertyCopyTest(user,target,sum);cglibBeanCopyTest(user,target,sum);springBeanCopyTest(user,target,sum);}private static void apacheBeanUtilsCopyTest(User source, User target, int sum) {StopWatch stopWatch new StopWatch();stopWatch.start();for (int i 0; i sum; i) {apacheBeanUtilsCopy(source,target);}stopWatch.stop();System.out.println(使用org.apache.commons.beanutils.BeanUtils方式赋值sum个user对象耗时stopWatch.getLastTaskTimeMillis()毫秒);}/*** org.apache.commons.beanutils.BeanUtils方式*/private static void apacheBeanUtilsCopy(User source, User target) {try {BeanUtils.copyProperties(source, target);} catch (Exception e) {}}private static void commonsPropertyCopyTest(User source, User target, int sum) {StopWatch stopWatch new StopWatch();stopWatch.start();for (int i 0; i sum; i) {commonsPropertyCopy(source,target);}stopWatch.stop();System.out.println(使用org.apache.commons.beanutils.PropertyUtils方式赋值sum个user对象耗时stopWatch.getLastTaskTimeMillis()毫秒);}/*** org.apache.commons.beanutils.PropertyUtils方式*/private static void commonsPropertyCopy(User source, User target) {try {PropertyUtils.copyProperties(target, source);} catch (Exception e) {}}private static void cglibBeanCopyTest(User source, User target, int sum) {StopWatch stopWatch new StopWatch();stopWatch.start();for (int i 0; i sum; i) {cglibBeanCopy(source,target);}stopWatch.stop();System.out.println(使用org.springframework.cglib.beans.BeanCopier方式赋值sum个user对象耗时stopWatch.getLastTaskTimeMillis()毫秒);}/*** org.springframework.cglib.beans.BeanCopier方式*/static BeanCopier copier BeanCopier.create(User.class, User.class, false);private static void cglibBeanCopy(User source, User target) {copier.copy(source, target, null);}private static void springBeanCopyTest(User source, User target, int sum) {StopWatch stopWatch new StopWatch();stopWatch.start();for (int i 0; i sum; i) {springBeanCopy(source,target);}stopWatch.stop();System.out.println(使用org.springframework.beans.BeanUtils.copyProperties方式赋值sum个user对象耗时stopWatch.getLastTaskTimeMillis()毫秒);}/*** org.springframework.beans.BeanUtils.copyProperties方式*/private static void springBeanCopy(User source, User target) {org.springframework.beans.BeanUtils.copyProperties(source, target);}
}“四大金刚” 性能统计
不测不知道一测吓一跳差的还真的多。
spring cglib BeanCopier性能最好apache BeanUtils性能最差。
性能走势 -- spring cglib BeanCopier优于 spring copyProperties优于 apache PropertyUtils优于 apache BeanUtils
避免用Apache Beanutils进行属性的copy的问题 上面分析完了下面再看看其它的方法做了哪些优化。
Apache PropertyUtils 源码分析 从源码可以清晰的看到类型检查变成了非空校验去掉了每一次copy的日志记录性能肯定更好了。
类型检查变成了非空校验 去掉了每一次copy的日志记录 实际赋值的地方由copyProperty变成了DanyBean setSimpleProperty DanyBean 提供了可以动态修改实现他的类的属性名称、属性值、属性类型的功能。
public void copyProperties(Object dest, Object orig) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {// 判断数据源和目标对象不是nullif (dest null) {throw new IllegalArgumentException(No destination bean specified);} else if (orig null) {throw new IllegalArgumentException(No origin bean specified);} else {// 删除了org.apache.commons.beanutils.BeanUtils.copyProperties中最为耗时的log日志记录int var5;int var6;String name;Object value;// 类型检查if (orig instanceof DynaBean) {// 获取源对象所有属性DynaProperty[] origDescriptors ((DynaBean)orig).getDynaClass().getDynaProperties();DynaProperty[] var4 origDescriptors;var5 origDescriptors.length;for(var6 0; var6 var5; var6) {DynaProperty origDescriptor var4[var6];// 获取源对象属性名name origDescriptor.getName();// 判断源对象是否可读、判断目标对象是否可写if (this.isReadable(orig, name) this.isWriteable(dest, name)) {try {// 获取对应的值value ((DynaBean)orig).get(name);// 相对于org.apache.commons.beanutils.BeanUtils.copyProperties此处有优化// DanyBean 提供了可以动态修改实现他的类的属性名称、属性值、属性类型的功能if (dest instanceof DynaBean) {((DynaBean)dest).set(name, value);} else {// 每个属性都调用一次copyPropertythis.setSimpleProperty(dest, name, value);}} catch (NoSuchMethodException var12) {if (this.log.isDebugEnabled()) {this.log.debug(Error writing to name on class dest.getClass() , var12);}}}}} else if (orig instanceof Map) {Iterator entries ((Map)orig).entrySet().iterator();while(true) {Map.Entry entry;String name;do {if (!entries.hasNext()) {return;}entry (Map.Entry)entries.next();name (String)entry.getKey();} while(!this.isWriteable(dest, name));try {if (dest instanceof DynaBean) {((DynaBean)dest).set(name, entry.getValue());} else {this.setSimpleProperty(dest, name, entry.getValue());}} catch (NoSuchMethodException var11) {if (this.log.isDebugEnabled()) {this.log.debug(Error writing to name on class dest.getClass() , var11);}}}} else {PropertyDescriptor[] origDescriptors this.getPropertyDescriptors(orig);PropertyDescriptor[] var16 origDescriptors;var5 origDescriptors.length;for(var6 0; var6 var5; var6) {PropertyDescriptor origDescriptor var16[var6];name origDescriptor.getName();if (this.isReadable(orig, name) this.isWriteable(dest, name)) {try {value this.getSimpleProperty(orig, name);if (dest instanceof DynaBean) {((DynaBean)dest).set(name, value);} else {this.setSimpleProperty(dest, name, value);}} catch (NoSuchMethodException var10) {if (this.log.isDebugEnabled()) {this.log.debug(Error writing to name on class dest.getClass() , var10);}}}}}}
}通过 jvisualvm.exe 检测代码性能 再通过jvisualvm.exe检测一下运行情况果然logging.log4j没有了其他的基本不变。 Spring copyProperties 源码分析 判断数据源和目标对象的非空判断改为了断言 每次copy没有日志记录 没有if (orig instanceof DynaBean) {这个类型检查 增加了放开权限的步骤
private static void copyProperties(Object source, Object target, Nullable Class? editable,Nullable String... ignoreProperties) throws BeansException {// 判断数据源和目标对象不是nullAssert.notNull(source, Source must not be null);Assert.notNull(target, Target must not be null);/*** 若target设置了泛型则默认使用泛型* 若是 editable 是 null则此处忽略* 一般情况下editable都默认为null*/Class? actualEditable target.getClass();if (editable ! null) {if (!editable.isInstance(target)) {throw new IllegalArgumentException(Target class [ target.getClass().getName() ] not assignable to Editable class [ editable.getName() ]);}actualEditable editable;}// 获取target中全部的属性描述PropertyDescriptor[] targetPds getPropertyDescriptors(actualEditable);// 需要忽略的属性ListString 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 getPropertyDescriptor(source.getClass(), targetPd.getName());if (sourcePd ! null) {Method readMethod sourcePd.getReadMethod();/*** 源对象存在读取方法、数据是可复制的* writeMethod.getParameterTypes()[0]获取 writeMethod 的第一个入参类型* readMethod.getReturnType()获取 readMethod 的返回值类型* 判断返回值类型和入参类型是否存在继承关系只有是继承关系或相等的情况下才会进行注入*/if (readMethod ! null ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], 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);}// 通过反射写入值writeMethod.invoke(target, value);}catch (Throwable ex) {throw new FatalBeanException(Could not copy property targetPd.getName() from source to target, ex);}}}}}
}总结
阿里的友情提示避免用Apache Beanutils进行对象的copy还是很有道理的。Apache Beanutils的性能问题出现在类型校验和每一次copy的日志记录Apache PropertyUtils 进行了如下优化
类型检查变成了非空校验
去掉了每一次copy的日志记录
实际赋值的地方由copyProperty变成了DanyBean setSimpleProperty
Spring copyProperties 进行了如下优化
判断数据源和目标对象的非空判断改为了断言
每次copy没有日志记录
没有if (orig instanceof DynaBean) {这个类型检查
增加了放开权限的步骤